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:
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