Right Now the lib is in a usable state. I'm currently looking for feedback on the overall structure and usability of the lib from you guys. My own use case has been along some pretty set lines, so I expect i'm missing some usability.
In the spirit of helping everyone else, my ambition is to make this a class lib that makes it relatively easy for anyone with a bit of experience with ahk to get rolling with any given serial/COM project they may wish to pursue.
So here is my initial offer as far as methods I believe will be useful (keep in mind there are still a few lines in the code which use OLD obsolete commands which I have not yet replaced. The most obvious of these being the SetFormat command. I do plan to replace these but have not had a chance to do full testing with the updated code yet) Again I'm mostly looking for feedback on structure and usability. But I'll take any constructive criticism.
Code: Select all
/*
Class SerialCOM
Created by ShatterCoder
This class was built on the original work of the creator of Seral.ahk https://autohotkey.com/board/topic/26231-serial-com-port-console-script/page-2
Methods:
***************************************************************************************************************************************************************
Event_Parse_Start(Register_Function := "__NONE__", Delims := "`r`n", Omit_Chars := "", Seperator := "`n", Poll_Interval := 100)
This Method is intended to continuously recieve and parse data from the COM port specified by the obj.COM_port property
Resister_Function - Pass the name of a function no paren's () to register it to be called each time a "chunk" of data has been recieved
Chunks are defined by the Delims Param defined below
Default: No function is called, instead all data is stored in obj.AllRecieved and obj._Recieved_Partial will have any partial messages remaining
if you specify a function name each time a chunk is parsed it will call your function and pass it the chunk as the only parameter
Delims - This parameter is used to pass the characters you would like to use to define the end of a "chunk" of serial communication
Defualt: `r`n
Omit_Chars - Used to signify characters you would like to ommit from your comminication
Default: by default no chars are ommited
Seperator - Used to specify how chunks are seperated in this.AllRecieved
Default: `n
Poll_Interval - interval in ms to wait between each reading of the serial register
Default: 100 ms
Example Usage:
Com3 := New SerialCOM("COM3")
Com3.Event_Parse_Start("MyFunc", "`n")
MyFunc(Input)
{
msgbox, % "sweet! just recieved the following: `n" Input
return
}
Esc::
Com3.Event_Parse_Stop()
return
***************************************************************************************************************************************************************
Event_Parse_Stop()
Simple method to stop the event reader.
***************************************************************************************************************************************************************
Send_Message(asciiMessage)
One stop method will open the COM port, send the message, then close the COM port once finished,
meant for infrequently sent communications.
***************************************************************************************************************************************************************
Begin_Send_Stream() ; send_to_stream(asciiMessage) ; Close_Send_Stream()
These 3 methods are used to send constant data, obj.Begin_Send_Stream() Opens the port and allows you to send data via
obj.send_to_stream() When you are finished sending simply call the obj.Close_Send_Stream() method to Close out the port.
asciiMessage - any number or string you wish to send over the COM port. it's automatically converted to hex and sent
Example Usage:
MyCom := New SerialCOM()
MyCom.Begin_Send_Stream()
Loop, 1000
{
MyCom.send_to_stream(A_Now)
sleep, 1000
}
MyCom.Close_Send_Stream()
***************************************************************************************************************************************************************
*/
Class SerialCOM
{
__AscToHex(str) {
Return str="" ? "":Chr((Asc(str)>>4)+48) Chr((x:=Asc(str)&15)+(x>9 ? 55:48)) this.__AscToHex(SubStr(str,2))
}
__New( Port := "COM1", Baud := 9600, Parity := "N", Data := 8, Stop := 1)
{
this._Recieved_Partial := ""
this.COM_Port := Port
this.Baud_Rate := Baud
this.Parity := "N"
this.Data_Bits := Data
this.Stop_Bits := Stop
this.COM_FileHandle := ""
this.Bytes_Recieved := ""
}
__Delete()
{
if this.COM_FileHandle != ""
this.__Close_COM()
}
__Open_Port()
{
if this.COM_FileHandle != ""
return -1
Settings_String := this.COM_Port ":baud=" this.Baud_Rate " parity=" this.Parity " data=" this.Data_Bits " stop=" this.Stop_Bits " dtr=Off"
;###### Build COM DCB ######
;Creates the structure that contains the COM Port number, baud rate,...
VarSetCapacity(DCB, 28)
While (BCD_Result != 1)
{
BCD_Result := DllCall("BuildCommDCB" ,"str" , Settings_String,"UInt", &DCB)
if (BCD_Result != 1)
{
MsgBox, 262196,COM failure ,it appears that the device may not be connected to %COM_Port%. Would you like to change COM ports?
IfMsgBox, Yes
{
InputBox, COM_Port, Select New COM port, Which COM port should be selected(COM1`, COM2 etc. Note ALLCAPS and no spaces)?
}
IfMsgBox, No
return 0
}
}
COM_Port_Len := StrLen(This.COM_Port) ;For COM Ports > 9 \\.\ needs to prepended to the COM Port name.
If COM_Port_Len > 4) ;So the valid names are
This.COM_Port := "\\.\" This.COM_Port ; ... COM8 COM9 \\.\COM10 \\.\COM11 \\.\COM12 and so on...
Else ;
This.COM_Port := This.COM_Port
This.COM_FileHandle := DllCall("CreateFile"
,"Str" , This.COM_Port ;File Name
,"UInt", 0xC0000000 ;Desired Access
,"UInt", 3 ;Share Mode
,"UInt", 0 ;Security Attributes
,"UInt", 3 ;Creation Disposition
,"UInt", 0 ;Flags And Attributes
,"UInt", 0 ;Template File
,"Cdecl Int")
While (this.COM_FileHandle < 1)
{
This.COM_FileHandle := DllCall("CreateFile"
,"Str" , This.COM_Port ;File Name
,"UInt", 0xC0000000 ;Desired Access
,"UInt", 3 ;Share Mode
,"UInt", 0 ;Security Attributes
,"UInt", 3 ;Creation Disposition
,"UInt", 0 ;Flags And Attributes
,"UInt", 0 ;Template File
,"Cdecl Int")
if (A_index > 20) ;times out after ~ 2 seconds
{
MsgBox, % "There is a problem with Serial Port communication. `nFailed Dll CreateFile, COM_FileHandle=" this.COM_FileHandle " `nThe Script Will Now Exit."
Exit
}
sleep, 100
}
SCS_Result := DllCall("SetCommState"
,"UInt", this.COM_FileHandle ;File Handle
,"UInt", &DCB) ;Pointer to DCB structure
If (SCS_Result <> 1)
{
MsgBox, There is a problem with Serial Port communication. `nFailed Dll SetCommState, SCS_Result=%SCS_Result% `nThe Script Will Now Exit.
this.__Close_COM()
ErrorDrivenRetry := 1
return
}
;###### Create the SetCommTimeouts Structure ######
ReadIntervalTimeout = 0xffffffff
ReadTotalTimeoutMultiplier = 0x00000000
ReadTotalTimeoutConstant = 0x00000000
WriteTotalTimeoutMultiplier= 0x00000000
WriteTotalTimeoutConstant = 0x00000000
VarSetCapacity(Data, 20, 0) ; 5 * sizeof(DWORD)
NumPut(ReadIntervalTimeout, Data, 0, "UInt")
NumPut(ReadTotalTimeoutMultiplier, Data, 4, "UInt")
NumPut(ReadTotalTimeoutConstant, Data, 8, "UInt")
NumPut(WriteTotalTimeoutMultiplier, Data, 12, "UInt")
NumPut(WriteTotalTimeoutConstant, Data, 16, "UInt")
;###### Set the COM Timeouts ######
SCT_result := DllCall("SetCommTimeouts"
,"UInt", this.COM_FileHandle ;File Handle
,"UInt", &Data) ;Pointer to the data structure
If (SCT_result <> 1)
{
MsgBox, There is a problem with Serial Port communication. `nFailed Dll SetCommState, SCT_result=%SCT_result% `nThe Script Will Now Exit.
this.__Close_COM()
Exit
}
}
__Close_COM()
{
;###### Close the COM File ######
CH_result := DllCall("CloseHandle", "UInt", this.COM_FileHandle)
If (CH_result <> 1)
MsgBox, % "Failed Dll CloseHandle CH_result=" CH_result
this.COM_FileHandle := ""
Return
}
__Write_to_COM(Message)
{
SetFormat, Integer, DEC
Data_Length := 1
Loop, Parse, Message, `,
{
Data_Length ++
}
;Set the Data buffer size, prefill with 0xFF.
VarSetCapacity(Data, Data_Length, 0xFF)
Loop, Parse, Message, `,
{
NumPut(A_loopfield, Data, (A_index-1), "UChar")
}
;###### Write the data to the COM Port ######
WF_Result := DllCall("WriteFile"
,"UInt" , this.COM_FileHandle ;File Handle
,"UInt" , &Data ;Pointer to string to send
,"UInt" , Data_Length ;Data Length
,"UInt*", Bytes_Sent ;Returns pointer to num bytes sent
,"Int" , "NULL")
If (WF_Result != 1 or Bytes_Sent != Data_Length)
{
Sleep, 10
WF_Result := DllCall("WriteFile"
,"UInt" , this.COM_FileHandle ;File Handle
,"UInt" , &Data ;Pointer to string to send
,"UInt" , Data_Length ;Data Length
,"UInt*", Bytes_Sent ;Returns pointer to num bytes sent
,"Int" , "NULL")
If (WF_Result <> 1 or Bytes_Sent <> Data_Length)
MsgBox, % "Failed Dll WriteFile to " this.COM_Port ", result=" WF_Result " `nData Length=" Data_Length " `nBytes_Sent=" Bytes_Sent
}
}
__Read_from_COM(Num_Bytes)
{
SetFormat, Integer, HEX
this.Bytes_Received := 0
;Set the Data buffer size, prefill with 0x55 = ASCII character "U"
;VarSetCapacity won't assign anything less than 3 bytes. Meaning: If you
; tell it you want 1 or 2 byte size variable it will give you 3.
Data_Length := VarSetCapacity(Data, Num_Bytes, 0x55)
;~ msgbox, Data_Length=%Num_Bytes%
;~ MsgBox, % this.COM_FileHandle
;###### Read the data from the COM Port ######
Read_Result := DllCall("ReadFile"
,"UInt" , this.COM_FileHandle ; hFile
,"Str" , Data ; lpBuffer
,"Int" , Num_Bytes ; nNumberOfBytesToRead
,"UInt*", Bytes_Received ; lpNumberOfBytesReceived
,"Int" , 0) ; lpOverlapped
sleep, 10
this.Bytes_Received := Bytes_Received
If (Read_Result != 1)
{
MsgBox, % "There is a problem with Serial Port communication. `nFailed Dll ReadFile on " COM_Port ", result=" Read_Result " - The Script Will Now Exit."
this.__Close_COM()
Exit
}
;~ if this.Bytes_Received != 0
;~ MsgBox, % this.Bytes_Received
i := 0
Data_HEX := ""
Loop % this.Bytes_Received
{
;~ MsgBox, % Data
;First byte into the Rx FIFO ends up at position 0
Data_HEX_Temp := NumGet(Data, i, "UChar") ;Convert to HEX byte-by-byte
Data_HEX_Temp := SubStr(Data_HEX_Temp, 3)
;~ StringTrimLeft, Data_HEX_Temp, Data_HEX_Temp, 2 ;Remove the 0x (added by the above line) from the front
;If there is only 1 character then add the leading "0'
Length := StrLen(Data_HEX_Temp)
If (Length =1)
Data_HEX_Temp := "0" Data_HEX_Temp
i++
;Put it all together
Data_HEX := Data_HEX Data_HEX_Temp
}
SetFormat, Integer, DEC
if (Data_HEX != "")
{
this.Last_Recieved_HEX := Data_HEX
;~ Clipboard := Data_HEX
;~ MsgBox, pause
}
Return Data_HEX
}
Event_Parse_Start(Register_Function := "__NONE__", Delims := "`r`n", Omit_Chars := "", Seperator := "`n", Poll_Interval := 100)
{
this._Event_delims := Delims
this._Event_Omit_Chars := Omit_Chars
this._Event_Registered_Function := Register_Function
this._Event_Seperator := Seperator
this.__Open_Port()
this.__start_timer(Poll_Interval)
;~ settimer, this.__Check_Read_Register, Poll_Interval
}
__start_timer(Poll_Interval)
{
mthd := this.__Check_Read_Register.bind(this)
this.__timer_handle := mthd
SetTimer, % mthd, % Poll_Interval
}
Event_Parse_Stop()
{
mthd := this.__timer_handle
settimer, % mthd, off
this.__Close_COM()
}
__Check_Read_Register()
{
mthd := this.__timer_handle
settimer, % mthd, off
ReceivedMessage := this.__Read_from_COM("0xFF")
Translated := this.__HexToASCII(ReceivedMessage)
AsciiOut := ""
Registered_Function := this._Event_Registered_Function
if this._Recieved_Partial != ""
Translated := this._Recieved_Partial Translated
test := StrSplit(Translated, this._Event_delims)
if (test.count() > 1)
{
loop, Parse, Translated,% this._Event_delims, % this._Event_Omit_Chars
{
if (A_index = test.count() && A_LoopField != "" )
this._Recieved_Partial := A_Loopfield ;this is wrong need a clear head to figure out the right way to do it.
if (A_loopfield != "")
{
AsciiOut .= A_loopfield this._Event_Seperator
this._Recieved_Partial := ""
this.AllRecieved .= AsciiOut
}
if (this._Event_Registered_Function != "__NONE__" && A_LoopField != "")
%Registered_Function%(A_loopfield)
}
}
else
this._Recieved_Partial := Translated
SetTimer, % mthd, % Poll_Interval
return
}
__HexToASCII(ReceivedMessage)
{
loopcount := StrLen(ReceivedMessage) / 2
AsciiTranslation := ""
loop, % loopcount
{
CurrentHex := SubStr(ReceivedMessage, 1, 2)
;~ StringMid, CurrentHex, ReceivedMessage, 1, 2
ReceivedMessage := SubStr(ReceivedMessage, 3)
;~ StringTrimLeft, ReceivedMessage, ReceivedMessage, 2
CurrentHex := "0x" CurrentHex ;format the hex code so ahk will recognize it as hex
CurrentAscii := chr(CurrentHex)
AsciiTranslation .= CurrentAscii ;creates a single raw string of translated (into ASCII) text. Further formating may be needed
}
return AsciiTranslation
}
Send_Message(asciiMessage)
{
rawHex := this.__AscToHex(asciiMessage)
hexLngth := % StrLen(rawHex)/2
hexLngth :=Floor(hexLngth)
this.__Open_Port()
Message := "0x"
while, hexLngth>A_Index-1
{
if(A_index > 1)
Message .= ", 0x"
StringMid, ValueToAdd, rawHex, A_Index*2-1, 2
Message .= ValueToAdd
}
this.__Write_to_COM(Message)
this.__Close_COM()
}
Begin_Send_Stream()
{
this.__Open_Port()
return 1
}
Close_Send_Stream()
{
this.__Close_COM()
return 1
}
send_to_stream(asciiMessage)
{
rawHex := this.__AscToHex(asciiMessage)
hexLngth := % StrLen(rawHex)/2
hexLngth :=Floor(hexLngth)
this.__Open_Port()
Message := "0x"
while, hexLngth>A_Index-1
{
if(A_index > 1)
Message .= ", 0x"
StringMid, ValueToAdd, rawHex, A_Index*2-1, 2
Message .= ValueToAdd
}
this.__Write_to_COM(Message)
}
}