3D Scanner Turntable for DAVID5 (now HP 3D Scan)

With the change from DAVID version 4 to DAVID version 5, the COM port interface for turntables has disappeared. Well, I already have a turntable, and so do may others, so here I will detail how to get your turntable back in business using the one I build for DAVID4 as an example.

Lets see what we need. In DAVID, there is a 'Scan' button that will perform the next scan. When it is done, the model has to be turned somehow and button can be pressed again to acquire the next scan.

To automate this we will need to have access to

- programmatically press the Scan button

- automatically determine when the previous scan is finished

- have access to the serial port to send commands to the turntable

We will do this using a tool called AutoIt

https://www.autoitscript.com/site/autoit/

Here is a basic AutoIt script that will do nothing but set up a window and a loop to check if it has been signaled to close:

#include <GUIConstantsEx.au3>
Local $hGUI = GUICreate("DAVID Laserscanner 5 Turntable Automator",500,500)
GUISetState(@SW_SHOW, $hGUI) ; Display the GUI.
While 1  ; Loop until the user exits.
 Switch GUIGetMsg()
  Case $GUI_EVENT_CLOSE
    ExitLoop
 EndSwitch
 Sleep (50) ; limit the CPU load by restricting the number of GUIGetMsg calls per second
WEnd

AutoIt can send button presses by using the MouseClick command. However, this command needs an x and y coordinate (where to click). So the first order of business is to find out at which coordinates the Scan button is. To do that, we use AutoIt to display a tool tip with the current mouse position right next to the mouse cursor

$position = MouseGetPos () ; read current mouse position

 Tooltip ($position[0] & ", " & $position[1]) ; make a tool tip displaying current mouse position

Now we need a place to put these coordinates into our window (with a suitable default).

#include <GUIConstantsEx.au3>
#include <EditConstants.au3>
Local $hGUI = GUICreate("DAVID Laserscanner 5 Turntable Automator",500,500)
$x_coordinate = GUICtrlCreateInput("120", 220, 20, 90, 20, $ES_NUMBER )
$y_coordinate = GUICtrlCreateInput("275", 220, 50, 90, 20, $ES_NUMBER )
GUICtrlCreateLabel("X coordinate of Scan button in DAVID:", 20, 20+2)
GUICtrlCreateLabel("Y coordinate of Scan button in DAVID:", 20, 50+2)
GUISetState(@SW_SHOW, $hGUI) ; Display the GUI.

And then we can make a button which, if pressed, presses the Scan button.

#include <GUIConstantsEx.au3>
#include <EditConstants.au3>
#include <AutoItConstants.au3>
Local $hGUI = GUICreate("DAVID Laserscanner 5 Turntable Automator",500,500)
$x_coordinate = GUICtrlCreateInput("120", 220, 20, 90, 20, $ES_NUMBER )
$y_coordinate = GUICtrlCreateInput("275", 220, 50, 90, 20, $ES_NUMBER )
GUICtrlCreateLabel("X coordinate of Scan button in DAVID:", 20, 20+2)
GUICtrlCreateLabel("Y coordinate of Scan button in DAVID:", 20, 50+2)
Local $button = GUICtrlCreateButton("Scan", 350, 20, 100, 45)
GUISetState(@SW_SHOW, $hGUI) ; Display the GUI.
While 1  ; Loop until the user exits.
 Switch GUIGetMsg()
  Case $GUI_EVENT_CLOSE
    ExitLoop
  Case  $button
    MouseClick($MOUSE_CLICK_LEFT, GUICtrlRead($x_coordinate), GUICtrlRead($y_coordinate), 1)
 EndSwitch
 $position = MouseGetPos () ; read current mouse position
 Tooltip ($position[0] & ", " & $position[1]) ; make a tool tip displaying current mouse position
 Sleep (50) ; limit the CPU load by restricting the number of GUIGetMsg calls per second
WEnd

Next, we need to know when the scan is finished, so that we can send a signal to the turntable and then send another button press. Do do this, remember that applications usually call a myriad of functions provided by the operating system to help them do their work. At the very least, I would expect DAVID to call some graphics functions and either memory allocation or file access to facilitate what it does during the scan. To see what it actually does during a scan, I used API monitor:

http://www.rohitab.com/apimonitor

Every scan, DAVID accesses a font file as well and creates a preview PNG file. It turns out that the font is accessed at other times too, but we can rely on the fact that after making the preview file, it won't access the font file until the scan is finished. So, if we could monitor the CreateFile function call Windows provides all we would have to do is wait for a PNG file creation, then wait one more font access and would know we were ready to run another scan.

We can do this using AutoIt with a tool called Deviare.

http://www.nektra.com/products/deviare-api-hook-windows/

We start using the code from here:

#include <MsgBoxConstants.au3>
 
$comObject = ObjCreate("DeviareCOM.NktSpyMgr")   ; Create an COM object to access the Deviare interface
$comObject.Initialize
$eventObject=ObjEvent($comObject,"Deviare2_","DNktSpyMgrEvents")  ; events from comObject are now passed to eventObject
 
While 1
    Sleep(100)
WEnd
 
Volatile Func Deviare2_OnProcessStarted($process)
; for reference on the proc object: http://www.nektra.com/products/deviare-api-hook-windows/doc-v2/interface_i_nkt_process.html
  if ($process.Name == "notepad.exe") then
    $hook = $comObject.CreateHook("kernel32.dll!CreateFileW", 0)
    $hook.Attach($process, True)
    $hook.Hook()
    MsgBox($MB_SYSTEMMODAL, $process.name, "Target Process started and hooked", 10)
  EndIf
EndFunc
 
Volatile Func Deviare2_OnFunctionCalled($hook, $process, $callInfo)
   $Params = $callInfo.Params
   $Count = $Params.Count
   $First = $Params.First
   $Second = $Params.Next
   $Third = $Params.Next
   $TitleString = "Number of parameters: " & $Count
   $MessageString = "Parameter: " & $First.name & " Value: " & $First.value & @LF
   $MessageString = $MessageString & "Parameter: " & $Second.name & " Value: " & $Second.value & @LF
   $MessageString = $MessageString & "Parameter: " & $Third.name & " Value: " & $Third.value
   MsgBox($MB_SYSTEMMODAL, $TitleString, $MessageString, 10)
EndFunc

And then we can add relevant parts to the code we have developed above. The process we are intercepting is not notepad.exe any more, but (in the latest update) HP3DScan5.exe

For previous versions of DAVID5 it would be DAVID5.exe or something similar.

#include <GUIConstantsEx.au3>
#include <EditConstants.au3>
#include <AutoItConstants.au3>
#include <MsgBoxConstants.au3>
Local $hGUI = GUICreate("DAVID Laserscanner 5 Turntable Automator",500,500)
$x_coordinate = GUICtrlCreateInput("120", 220, 20, 90, 20, $ES_NUMBER )
$y_coordinate = GUICtrlCreateInput("275", 220, 50, 90, 20, $ES_NUMBER )
GUICtrlCreateLabel("X coordinate of Scan button in DAVID:", 20, 20+2)
GUICtrlCreateLabel("Y coordinate of Scan button in DAVID:", 20, 50+2)
Local $idScan = GUICtrlCreateButton("Scan", 350, 20, 100, 45)
GUISetState(@SW_SHOW, $hGUI) ; Display the GUI.
$comObject = ObjCreate("DeviareCOM.NktSpyMgr")   ; Create an COM object to access the Deviare interface
$comObject.Initialize
$eventObject=ObjEvent($comObject,"Deviare2_","DNktSpyMgrEvents")  ; events from comObject are now passed to eventObject
While 1  ; Loop until the user exits.
 Switch GUIGetMsg()
  Case $GUI_EVENT_CLOSE
    ExitLoop
  Case  $idScan
    MouseClick($MOUSE_CLICK_LEFT, GUICtrlRead($x_coordinate), GUICtrlRead($y_coordinate), 1)
 EndSwitch
 $position = MouseGetPos () ; read current mouse position
 Tooltip ($position[0] & ", " & $position[1]) ; make a tool tip displaying current mouse position
 Sleep (50) ; limit the CPU load by restricting the number of GUIGetMsg calls per second
WEnd
Volatile Func Deviare2_OnProcessStarted($process)
; for reference on the proc object: http://www.nektra.com/products/deviare-api-hook-windows/doc-v2/interface_i_nkt_process.html
  if ($process.Name == "HP3DScan5.exe") then
    $hook = $comObject.CreateHook("kernel32.dll!CreateFileW", 0)
    $hook.Attach($process, True)
    $hook.Hook()
    MsgBox($MB_SYSTEMMODAL, $process.name, "HP3DScan5.exe started and hooked", 10)
  EndIf
EndFunc
Volatile Func Deviare2_OnFunctionCalled($hook, $process, $callInfo)
   MsgBox($MB_SYSTEMMODAL, "CreateFile called", "CreateFile called", 10)
EndFunc
Also, at this point it is useful to install an error handler to be able to tell if anything goes wrong with the COM subsystem:
; User's COM error function. Will be called if COM error occurs
; from https://www.autoitscript.com/forum/topic/134615-the-requested-action-with-this-object-has-failed/
$oMyError = ObjEvent("AutoIt.Error","MyErrFunc")    ; Initialize a COM error handler
Func MyErrFunc()
  Msgbox(0,"AutoItCOM Test","We intercepted a COM Error !"    & @CRLF  & @CRLF & _
             "err.description is: " & @TAB & $oMyError.description  & @CRLF & _
             "err.windescription:"   & @TAB & $oMyError.windescription & @CRLF & _
             "err.number is: "       & @TAB & hex($oMyError.number,8)  & @CRLF & _
             "err.lastdllerror is: "   & @TAB & $oMyError.lastdllerror   & @CRLF & _
            "err.scriptline is: "   & @TAB & $oMyError.scriptline   & @CRLF & _
             "err.source is: "       & @TAB & $oMyError.source       & @CRLF & _
             "err.helpfile is: "       & @TAB & $oMyError.helpfile     & @CRLF & _
             "err.helpcontext is: " & @TAB & $oMyError.helpcontext _
            )
Endfunc

So the code up to this point is:

#include <GUIConstantsEx.au3>
#include <EditConstants.au3>
#include <AutoItConstants.au3>
#include <MsgBoxConstants.au3>
Local $hGUI = GUICreate("DAVID Laserscanner 5 Turntable Automator",500,500)
$x_coordinate = GUICtrlCreateInput("120", 220, 20, 90, 20, $ES_NUMBER )
$y_coordinate = GUICtrlCreateInput("275", 220, 50, 90, 20, $ES_NUMBER )
GUICtrlCreateLabel("X coordinate of Scan button in DAVID:", 20, 20+2)
GUICtrlCreateLabel("Y coordinate of Scan button in DAVID:", 20, 50+2)
Local $idScan = GUICtrlCreateButton("Scan", 350, 20, 100, 45)
GUISetState(@SW_SHOW, $hGUI) ; Display the GUI.
$comObject = ObjCreate("DeviareCOM.NktSpyMgr")   ; Create an COM object to access the Deviare interface
$comObject.Initialize
$eventObject=ObjEvent($comObject,"Deviare2_","DNktSpyMgrEvents")  ; events from comObject are now passed to eventObject
While 1  ; Loop until the user exits.
 Switch GUIGetMsg()
  Case $GUI_EVENT_CLOSE
    ExitLoop
  Case  $idScan
    MouseClick($MOUSE_CLICK_LEFT, GUICtrlRead($x_coordinate), GUICtrlRead($y_coordinate), 1)
 EndSwitch
 $position = MouseGetPos () ; read current mouse position
 Tooltip ($position[0] & ", " & $position[1]) ; make a tool tip displaying current mouse position
 Sleep (50) ; limit the CPU load by restricting the number of GUIGetMsg calls per second
WEnd
Volatile Func Deviare2_OnProcessStarted($process)
; for reference on the proc object: http://www.nektra.com/products/deviare-api-hook-windows/doc-v2/interface_i_nkt_process.html
  if ($process.Name == "HP3DScan5.exe") then
    $hook = $comObject.CreateHook("kernel32.dll!CreateFileW", 0)
    $hook.Attach($process, True)
    $hook.Hook()
    MsgBox($MB_SYSTEMMODAL, $process.name, "HP3DScan5.exe started and hooked", 10)
  EndIf
EndFunc
Volatile Func Deviare2_OnFunctionCalled($hook, $process, $callInfo)
   MsgBox($MB_SYSTEMMODAL, "CreateFile called", "CreateFile called", 10)
EndFunc
; User's COM error function. Will be called if COM error occurs
; from https://www.autoitscript.com/forum/topic/134615-the-requested-action-with-this-object-has-failed/
$oMyError = ObjEvent("AutoIt.Error","MyErrFunc")    ; Initialize a COM error handler
Func MyErrFunc()
  Msgbox(0,"AutoItCOM Test","We intercepted a COM Error !"    & @CRLF  & @CRLF & _
   "err.description is: " & @TAB & $oMyError.description  & @CRLF & _
   "err.windescription:"   & @TAB & $oMyError.windescription & @CRLF & _
   "err.number is: "       & @TAB & hex($oMyError.number,8)  & @CRLF & _
   "err.lastdllerror is: "   & @TAB & $oMyError.lastdllerror   & @CRLF & _
   "err.scriptline is: "   & @TAB & $oMyError.scriptline   & @CRLF & _
   "err.source is: "       & @TAB & $oMyError.source       & @CRLF & _
   "err.helpfile is: "       & @TAB & $oMyError.helpfile     & @CRLF & _
   "err.helpcontext is: " & @TAB & $oMyError.helpcontext _
   )
Endfunc

Next, lets check for both the font access and the PNG creation in OnFunctionCalled

Volatile Func Deviare2_OnFunctionCalled($hook, $process, $callInfo)
  $Params = $callInfo.Params ; get the params member of the callInfo structure
  $First = $Params.First     ; get the first parameter passed to CreateFile
  $present = StringInStr($First.value,"PNG") ; check for presence of the substring "PNG" in the parameter.
  if ($present <> 0) then
   MsgBox($MB_SYSTEMMODAL, $Params.First.name, $Params.First.value, 10)
  EndIf
  $present = StringInStr($First.value,"font") ; check for presence of the substring "font" in the parameter.
  if ($present <> 0) then
   MsgBox($MB_SYSTEMMODAL, $Params.First.name, $Params.First.value, 10)
  EndIf
EndFunc

Before we go any further here, we need to develop some code to talk to the turntable via the serial port. There are some nice examples here:

https://www.autoitscript.com/wiki/CommAPI_Examples

There is a list of available COM Ports in the registry under

HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM

We proceed to add a control to the GUI on startup that lists them:

GUICtrlCreateLabel("COM Port:", 20, 80+2)
$comboBoxCOM  = GUICtrlCreateCombo("", 220, 70, 90, 20)
For $i = 1 To 100
    $sVar = RegEnumVal("HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM", $i)
    If @error <> 0 Then ExitLoop
    Local $sVar2 = RegRead("HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM", $sVar)
    $isCom = StringInStr($sVar2,"COM") ; check for presence of the substring "COM" in the key
    if ($isCom <> 0) then
      GUICtrlSetData($comboBoxCOM,$sVar2)
      _GUICtrlComboBox_SetEditText($comboBoxCOM,$sVar2)
    EndIf
Next

Next, we need to work on opening the serial port when the 'Scan' button is pressed. There is a collection of helper functions available under the CommAPI:

https://www.autoitscript.com/wiki/CommAPI

This requires the inclusion of several new files:

CommAPIConstants.au3
CommAPI.au3
CommAPIHelper.au3
CommUtilities.au3
CommInterface.au3
We can then add some code to open the COM port selected in the control and send the character '2' to it to perform the next rotational step.
Case  $idScan
    MouseClick($MOUSE_CLICK_LEFT, GUICtrlRead($x_coordinate), GUICtrlRead($y_coordinate), 1)
    $str = GUICtrlRead($comboBoxCOM)    ; get value of COM port from ComboBox
    $ret = StringRegExp($str, '\d+', 1) ; strip the 'COM' from the string
    Local $iPort = $ret[0]
    Local $iBaud = 9600
    Local $iParity = 0
    Local $iByteSize = 8
    Local $iStopBits = 0 ; note: some hardware does not support 1.5 stop bits.
    ; it is tempting to write 1 for stop bits but:
    ; ONESTOPBIT    0    1 stop bit.
    ; ONE5STOPBITS  1    1.5 stop bits.
    ; TWOSTOPBITS   2    2 stop bits.
    Local $sCommand = "2" & @CRLF
    Local $hFile = _CommAPI_OpenCOMPort($iPort, $iBaud, $iParity, $iByteSize, $iStopBits)
    If $hFile = 0 Then
        MsgBox(64, "Error in _CommAPI_OpenCOMPort" & @error, _WinAPI_GetLastErrorMessage())
    Else
      _CommAPI_ClearCommError($hFile)
      If @error Then
        MsgBox(32, "Error in _CommAPI_ClearCommError" & @error, _WinAPI_GetLastErrorMessage())
      EndIf
      _CommAPI_PurgeComm($hFile)
      If @error Then
        MsgBox(32, "Error in _CommAPI_PurgeComm" & @error, _WinAPI_GetLastErrorMessage())
      EndIf
      _CommAPI_TransmitString($hFile, $sCommand)
      If @error Then
        MsgBox(32, "Error in _CommAPI_TransmitString" & @error, _WinAPI_GetLastErrorMessage())
      EndIf
      Local $sResult = _CommAPI_ReceiveString($hFile, 5000)
      If @error Then
       Switch @error
        Case 0
            MsgBox(64, "Result5", SetError(@error, @ScriptLineNumber))
        Case -1
            MsgBox(32, "Error5", _WinAPI_GetLastErrorMessage())
        Case -2
            MsgBox(32, "Timeout5", SetError(@error, @ScriptLineNumber))
        Case Else
            MsgBox(32, "Error5", "Error " & @error & " in line " & $sResult)
       EndSwitch
      EndIf
      _CommAPI_ClosePort($hFile)
      If @error Then
       Switch @error
        Case 0
            MsgBox(64, "Result6", SetError(@error, @ScriptLineNumber))
        Case -1
            MsgBox(32, "Error6", _WinAPI_GetLastErrorMessage())
        Case -2
            MsgBox(32, "Timeout6", SetError(@error, @ScriptLineNumber))
        Case Else
            MsgBox(32, "Error6", "Error " & @error & " in line " & $sResult)
       EndSwitch
      EndIf
    EndIf
 EndSwitch
 $position = MouseGetPos () ; read current mouse position

One important thing to note is that the example given on the AutoIt site does this:

Local $hFile = _CommAPI_OpenCOMPort($iPort, $iBaud, $iParity, $iByteSize, $iStopBits)

with iStopBits defined as 1. However, a value of 1 will set the stop bits to 1.5. The most common setting for the stop bits on serial ports is for 1 stop bits. To achieve that, iStopBits needs to be set to 0.:

https://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx

The ChipKit board I am using here does not support 1.5 stop bits and so the connection will fail a bit further down the call stack with a very unhelpful error:

"The parameter is incorrect"

Without any indication which call is actually failing (SetCommState), nor which parameter is at fault(DCB.StopBits).

The code up to this point is this:

#include <GUIConstantsEx.au3>
#include <EditConstants.au3>
#include <AutoItConstants.au3>
#include <MsgBoxConstants.au3>
#include <GuiComboBox.au3>
#include "CommInterface.au3"
Local $hGUI = GUICreate("DAVID Laserscanner 5 Turntable Automator",500,500)
$x_coordinate = GUICtrlCreateInput("120", 220, 20, 90, 20, $ES_NUMBER )
$y_coordinate = GUICtrlCreateInput("275", 220, 45, 90, 20, $ES_NUMBER )
GUICtrlCreateLabel("X coordinate of Scan button in DAVID:", 20, 20+2)
GUICtrlCreateLabel("Y coordinate of Scan button in DAVID:", 20, 50+2)
GUICtrlCreateLabel("COM Port:", 20, 80+2)
$comboBoxCOM  = GUICtrlCreateCombo("", 220, 70, 90, 20)
For $i = 1 To 100
    $sVar = RegEnumVal("HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM", $i)
    If @error <> 0 Then ExitLoop
    Local $sVar2 = RegRead("HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM", $sVar)
    $isCom = StringInStr($sVar2,"COM") ; check for presence of the substring "COM" in the key
    if ($isCom <> 0) then
      GUICtrlSetData($comboBoxCOM,$sVar2)
      _GUICtrlComboBox_SetEditText($comboBoxCOM,$sVar2)
    EndIf
Next
Local $idScan = GUICtrlCreateButton("Scan", 350, 20, 100, 45)
GUISetState(@SW_SHOW, $hGUI) ; Display the GUI.
$comObject = ObjCreate("DeviareCOM.NktSpyMgr")   ; Create an COM object to access the Deviare interface
$comObject.Initialize
$eventObject=ObjEvent($comObject,"Deviare2_","DNktSpyMgrEvents")  ; events from comObject are now passed to eventObject
While 1  ; Loop until the user exits.
 Switch GUIGetMsg()
  Case $GUI_EVENT_CLOSE
    ExitLoop
  Case  $idScan
    MouseClick($MOUSE_CLICK_LEFT, GUICtrlRead($x_coordinate), GUICtrlRead($y_coordinate), 1)
    $str = GUICtrlRead($comboBoxCOM)    ; get value of COM port from ComboBox
    $ret = StringRegExp($str, '\d+', 1) ; strip the 'COM' from the string
    Local $iPort = $ret[0]
    Local $iBaud = 9600
    Local $iParity = 0
    Local $iByteSize = 8
    Local $iStopBits = 0 ; note: some hardware does not support 1.5 stop bits.
    ; it is tempting to write 1 for stop bits but:
    ; ONESTOPBIT    0    1 stop bit.
    ; ONE5STOPBITS  1    1.5 stop bits.
    ; TWOSTOPBITS   2    2 stop bits.
    Local $sCommand = "2" & @CRLF
    Local $hFile = _CommAPI_OpenCOMPort($iPort, $iBaud, $iParity, $iByteSize, $iStopBits)
    If $hFile = 0 Then
        MsgBox(64, "Error in _CommAPI_OpenCOMPort" & @error, _WinAPI_GetLastErrorMessage())
    Else
      _CommAPI_ClearCommError($hFile)
      If @error Then
        MsgBox(32, "Error in _CommAPI_ClearCommError" & @error, _WinAPI_GetLastErrorMessage())
      EndIf
      _CommAPI_PurgeComm($hFile)
      If @error Then
        MsgBox(32, "Error in _CommAPI_PurgeComm" & @error, _WinAPI_GetLastErrorMessage())
      EndIf
      _CommAPI_TransmitString($hFile, $sCommand)
      If @error Then
        MsgBox(32, "Error in _CommAPI_TransmitString" & @error, _WinAPI_GetLastErrorMessage())
      EndIf
      Local $sResult = _CommAPI_ReceiveString($hFile, 5000)
      If @error Then
       Switch @error
        Case 0
            MsgBox(64, "Result5", SetError(@error, @ScriptLineNumber))
        Case -1
            MsgBox(32, "Error5", _WinAPI_GetLastErrorMessage())
        Case -2
            MsgBox(32, "Timeout5", SetError(@error, @ScriptLineNumber))
        Case Else
            MsgBox(32, "Error5", "Error " & @error & " in line " & $sResult)
       EndSwitch
      EndIf
      _CommAPI_ClosePort($hFile)
      If @error Then
       Switch @error
        Case 0
            MsgBox(64, "Result6", SetError(@error, @ScriptLineNumber))
        Case -1
            MsgBox(32, "Error6", _WinAPI_GetLastErrorMessage())
        Case -2
            MsgBox(32, "Timeout6", SetError(@error, @ScriptLineNumber))
        Case Else
            MsgBox(32, "Error6", "Error " & @error & " in line " & $sResult)
       EndSwitch
      EndIf
    EndIf
 EndSwitch
 $position = MouseGetPos () ; read current mouse position
 Tooltip ($position[0] & ", " & $position[1]) ; make a tool tip displaying current mouse position
 Sleep (50) ; limit the CPU load by restricting the number of GUIGetMsg calls per second
WEnd
Volatile Func Deviare2_OnProcessStarted($process)
; for reference on the proc object: http://www.nektra.com/products/deviare-api-hook-windows/doc-v2/interface_i_nkt_process.html
  if ($process.Name == "HP3DScan5.exe") then
    $hook = $comObject.CreateHook("kernel32.dll!CreateFileW", 0)
    $hook.Attach($process, True)
    $hook.Hook()
    MsgBox($MB_SYSTEMMODAL, $process.name, "HP3DScan5.exe started and hooked", 10)
  EndIf
EndFunc
Volatile Func Deviare2_OnFunctionCalled($hook, $process, $callInfo)
  $Params = $callInfo.Params ; get the params member of the callInfo structure
  $First = $Params.First     ; get the first parameter passed to CreateFile
  $present = StringInStr($First.value,"PNG") ; check for presence of the substring "PNG" in the parameter.
  if ($present <> 0) then
   MsgBox($MB_SYSTEMMODAL, $Params.First.name, $Params.First.value, 10)
  EndIf
  $present = StringInStr($First.value,"font") ; check for presence of the substring "font" in the parameter.
  if ($present <> 0) then
   MsgBox($MB_SYSTEMMODAL, $Params.First.name, $Params.First.value, 10)
  EndIf
EndFunc
; User's COM error function. Will be called if COM error occurs
; from https://www.autoitscript.com/forum/topic/134615-the-requested-action-with-this-object-has-failed/
$oMyError = ObjEvent("AutoIt.Error","MyErrFunc")    ; Initialize a COM error handler
Func MyErrFunc()
  Msgbox(0,"AutoItCOM Test","We intercepted a COM Error !"    & @CRLF  & @CRLF & _
   "err.description is: " & @TAB & $oMyError.description  & @CRLF & _
   "err.windescription:"   & @TAB & $oMyError.windescription & @CRLF & _
   "err.number is: "       & @TAB & hex($oMyError.number,8)  & @CRLF & _
   "err.lastdllerror is: "   & @TAB & $oMyError.lastdllerror   & @CRLF & _
   "err.scriptline is: "   & @TAB & $oMyError.scriptline   & @CRLF & _
   "err.source is: "       & @TAB & $oMyError.source       & @CRLF & _
   "err.helpfile is: "       & @TAB & $oMyError.helpfile     & @CRLF & _
   "err.helpcontext is: " & @TAB & $oMyError.helpcontext _
   )
Endfunc

Now every time we press the 'Scan' button, the table rotates one position. Finally all we need to do is to automate the remaining position using the Deviare hook we made. The firmware is hard coded to 40 scans/revolution, so we need a counter to stop after 40 iterations. Also, we need an 'Abort' button in the GUI.

We start with some variables:

$arm = 0           ; variable to coordinate between scan button and Deviare callback
$numberOfScans = 40 ; we stop after 40
$doScan = 0        ; used as a signal by Deviare callback, so that the GUI thread knows to run another scan
and add to that the creation of another button
Local $idStop = GUICtrlCreateButton("Abort", 350, 57, 100, 32)
We move the execution of a rotational step and mouse click to a function
Func Scan ()
    $numberOfScans = $numberOfScans + 1
    $str = GUICtrlRead($comboBoxCOM)    ; get value of COM port from ComboBox
    .........
        EndSwitch
      EndIf
    EndIf
    Sleep(3000); now that we sent a turn command, lets wait a moment to give the table time to settle
             ; also, on slower computers it sometimes takes a little longer before the scan button is enabled again
    MouseClick($MOUSE_CLICK_LEFT, GUICtrlRead($x_coordinate), GUICtrlRead($y_coordinate), 1)
    Sleep (2000)   ; David acts the same at the beginning and the end of the scan. wait before arming the callback
    $arm = 1       ; arm the callback
EndFunc
and change the GUI loop:
 Switch GUIGetMsg()
  Case $GUI_EVENT_CLOSE
    ExitLoop
  Case  $idScan
    $numberOfScans = 0
    Scan()
  Case  $idStop
    $numberOfScans = 40
    $doScan = 0
    $arm = 0
 EndSwitch

and finally adjust the Deviare callback to trigger the next scan

Volatile Func Deviare2_OnFunctionCalled($hook, $process, $callInfo)
  $Params = $callInfo.Params ; get the params member of the callInfo structure
  $First = $Params.First     ; get the first parameter passed to CreateFile
  $present = StringInStr($First.value,"font") ; check for presence of the substring "font" in the parameter.
  if ($present <> 0) then
      if $arm = 1 Then        ; we are waiting for the end of a scan and have reached it
          $arm = 0
          if $numberOfScans < 40 Then
            ConsoleWrite ( $Params.First.name & $Params.First.value & @CRLF )
            $doScan = 1 ;signal GUI to scan
          EndIf
      EndIf
  EndIf
EndFunc

The code now looks like this and the program is done:

#include <GUIConstantsEx.au3>
#include <EditConstants.au3>
#include <AutoItConstants.au3>
#include <MsgBoxConstants.au3>
#include <GuiComboBox.au3>
#include "CommInterface.au3"
#include <Timers.au3>
$arm = 0           ; variable to coordinate between scan button and Deviare callback
$numberOfScans = 40 ; we stop after 40
$doScan = 0        ; used as a signal by Deviare callback, so that the GUI thread knows to run another scan
$hGUI = GUICreate("DAVID Laserscanner 5 Turntable Automator",500,120)
$x_coordinate = GUICtrlCreateInput("120", 220, 20, 90, 20, $ES_NUMBER )
$y_coordinate = GUICtrlCreateInput("300", 220, 45, 90, 20, $ES_NUMBER )
GUICtrlCreateLabel("X coordinate of Scan button in DAVID:", 20, 20+2)
GUICtrlCreateLabel("Y coordinate of Scan button in DAVID:", 20, 50+2)
GUICtrlCreateLabel("COM Port:", 20, 80+2)
$comboBoxCOM  = GUICtrlCreateCombo("", 220, 70, 90, 20)
For $i = 1 To 100
    $sVar = RegEnumVal("HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM", $i)
    If @error <> 0 Then ExitLoop
    Local $sVar2 = RegRead("HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM", $sVar)
    $isCom = StringInStr($sVar2,"COM") ; check for presence of the substring "COM" in the key
    if ($isCom <> 0) then
      GUICtrlSetData($comboBoxCOM,$sVar2)
      _GUICtrlComboBox_SetEditText($comboBoxCOM,$sVar2)
    EndIf
Next
Local $idScan = GUICtrlCreateButton("Scan", 350, 20, 100, 32)
Local $idStop = GUICtrlCreateButton("Abort", 350, 57, 100, 32)
GUISetState(@SW_SHOW, $hGUI) ; Display the GUI.
$comObject = ObjCreate("DeviareCOM.NktSpyMgr")   ; Create an COM object to access the Deviare interface
$comObject.Initialize
$eventObject=ObjEvent($comObject,"Deviare2_","DNktSpyMgrEvents")  ; events from comObject are now passed to eventObject
While 1  ; Loop until the user exits.
 Switch GUIGetMsg()
  Case $GUI_EVENT_CLOSE
    ExitLoop
  Case  $idScan
    $numberOfScans = 0
    Scan()
  Case  $idStop
    $numberOfScans = 40
    $doScan = 0
    $arm = 0
 EndSwitch
 $position = MouseGetPos () ; read current mouse position
 Tooltip ($position[0] & ", " & $position[1]) ; make a tool tip displaying current mouse position
 Sleep (50) ; limit the CPU load by restricting the number of GUIGetMsg calls per second
 if $doScan = 1 Then
     $doScan = 0
     Scan()
 EndIf
WEnd
Func Scan ()
    $numberOfScans = $numberOfScans + 1
    $str = GUICtrlRead($comboBoxCOM)    ; get value of COM port from ComboBox
    $ret = StringRegExp($str, '\d+', 1) ; strip the 'COM' from the string
    Local $iPort = $ret[0]
    Local $iBaud = 9600
    Local $iParity = 0
    Local $iByteSize = 8
    Local $iStopBits = 0 ; note: some hardware does not support 1.5 stop bits.
    ; it is tempting to write 1 for stop bits but:
    ; ONESTOPBIT    0    1 stop bit.
    ; ONE5STOPBITS  1    1.5 stop bits.
    ; TWOSTOPBITS   2    2 stop bits.
    Local $sCommand = "2" & @CRLF
    Local $hFile = _CommAPI_OpenCOMPort($iPort, $iBaud, $iParity, $iByteSize, $iStopBits)
    If $hFile = 0 Then
        MsgBox(64, "Error in _CommAPI_OpenCOMPort" & @error, _WinAPI_GetLastErrorMessage())
    Else
      _CommAPI_ClearCommError($hFile)
      If @error Then
        MsgBox(32, "Error in _CommAPI_ClearCommError" & @error, _WinAPI_GetLastErrorMessage())
      EndIf
      _CommAPI_PurgeComm($hFile)
      If @error Then
        MsgBox(32, "Error in _CommAPI_PurgeComm" & @error, _WinAPI_GetLastErrorMessage())
      EndIf
      _CommAPI_TransmitString($hFile, $sCommand)
      If @error Then
        MsgBox(32, "Error in _CommAPI_TransmitString" & @error, _WinAPI_GetLastErrorMessage())
      EndIf
      Local $sResult = _CommAPI_ReceiveString($hFile, 5000)
      If @error Then
       Switch @error
        Case 0
            MsgBox(64, "Result5", SetError(@error, @ScriptLineNumber))
        Case -1
            MsgBox(32, "Error5", _WinAPI_GetLastErrorMessage())
        Case -2
            MsgBox(32, "Timeout5", SetError(@error, @ScriptLineNumber))
        Case Else
            MsgBox(32, "Error5", "Error " & @error & " in line " & $sResult)
       EndSwitch
      EndIf
      _CommAPI_ClosePort($hFile)
      If @error Then
       Switch @error
        Case 0
            MsgBox(64, "Result6", SetError(@error, @ScriptLineNumber))
        Case -1
            MsgBox(32, "Error6", _WinAPI_GetLastErrorMessage())
        Case -2
            MsgBox(32, "Timeout6", SetError(@error, @ScriptLineNumber))
        Case Else
            MsgBox(32, "Error6", "Error " & @error & " in line " & $sResult)
       EndSwitch
      EndIf
    EndIf
    Sleep (3000)   ; now that we sent a turn command, lets wait a moment to give the table time to settle
                   ; also, on slower computers it sometimes takes a little longer before the scan button is enabled again
    MouseClick($MOUSE_CLICK_LEFT, GUICtrlRead($x_coordinate), GUICtrlRead($y_coordinate), 1)
    Sleep (2000)   ; David acts the same at the beginning and the end of the scan. wait before arming the callback
    $arm = 1       ; arm the callback
EndFunc
Volatile Func Deviare2_OnProcessStarted($process)
; for reference on the proc object: http://www.nektra.com/products/deviare-api-hook-windows/doc-v2/interface_i_nkt_process.html
  if ($process.Name == "HP3DScan5.exe") then
    $hook = $comObject.CreateHook("kernel32.dll!CreateFileW", 0)
    $hook.Attach($process, True)
    $hook.Hook()
  EndIf
EndFunc
Volatile Func Deviare2_OnFunctionCalled($hook, $process, $callInfo)
  $Params = $callInfo.Params ; get the params member of the callInfo structure
  $First = $Params.First     ; get the first parameter passed to CreateFile
  $present = StringInStr($First.value,"font") ; check for presence of the substring "font" in the parameter.
  if ($present <> 0) then
      if $arm = 1 Then        ; we are waiting for the end of a scan and have reached it
          $arm = 0
          if $numberOfScans < 40 Then
            ConsoleWrite ( $Params.First.name & $Params.First.value & @CRLF )
            $doScan = 1 ;signal GUI to scan
          EndIf
      EndIf
  EndIf
EndFunc
; User's COM error function. Will be called if COM error occurs
; from https://www.autoitscript.com/forum/topic/134615-the-requested-action-with-this-object-has-failed/
$oMyError = ObjEvent("AutoIt.Error","MyErrFunc")    ; Initialize a COM error handler
Func MyErrFunc()
  Msgbox(0,"AutoItCOM Test","We intercepted a COM Error !"    & @CRLF  & @CRLF & _
   "err.description is: " & @TAB & $oMyError.description  & @CRLF & _
   "err.windescription:"   & @TAB & $oMyError.windescription & @CRLF & _
   "err.number is: "       & @TAB & hex($oMyError.number,8)  & @CRLF & _
   "err.lastdllerror is: "   & @TAB & $oMyError.lastdllerror   & @CRLF & _
   "err.scriptline is: "   & @TAB & $oMyError.scriptline   & @CRLF & _
   "err.source is: "       & @TAB & $oMyError.source       & @CRLF & _
   "err.helpfile is: "       & @TAB & $oMyError.helpfile     & @CRLF & _
   "err.helpcontext is: " & @TAB & $oMyError.helpcontext _
   )
Endfunc