What is the best way to access a serial port from VBA?
I have a need for some of our sales reps to be able to send a simple string over the serial port from an action button in PowerPoint. I don't commonly use VBA, especially for anything like this. Normally I would turn it into an application of some sort, but I actually don't think the idea is that bad. It will be a handy tool for them to demo this device with while on a projector and talking to other sales guys and non technical people. Also, this sales guy will have no problem making small modifications to the VBA or PowerPoint presentation, but would not do as well with recompiling a .NET application.
I know we could do it through a batch file run from the presentation on the action, but that doesn't make me very happy. I figure we could probably access a COM object and run from there, but again I am not real up on the latest and greatest libraries to use in VBA, and it would also be nice to get a quick little primer in how to easily open, send and close the connection.
Since this will need to be run on multiple people's computers, it would be nice if it would be easily transportable to other machines. I should be able to say it has to run on Office 2007 and Windows XP. Compatibility with anything else would be a nice bonus though.
How should I go about handling this? Any good tips or tricks? Library recommendations?
The Win32 API handles the serial port as a file. You can access the serial ports directly by calling these API functions from within VBA. I had to do this for an old .NET application but VBA is no different.
Rather than hash it out for you on this site, here's a reference I've hung onto over the years. How to perform serial port communications in VBA
Sub Stinky()
Dim COM_Byte As Byte
Dim Received_Lines As Long
Dim Input_Buffer As String
Dim Output_Buffer As String
Dim Chars2Send As Long
Dim CharsRemaining As Long
Dim lfsr As Long
Open "COM7:9600,N,8,1" For Random As #1 Len = 1
Input_Buffer = ""
CharsRemaining = 0
Do
Get #1, , COM_Byte
If COM_Byte Then
If COM_Byte = 13 Then ' look for CR line termination
Debug.Print Input_Buffer, Now ' print it
Input_Buffer = "" ' and clear input buffer
' generate some output (9 characters)
lfsr = &H3FFFFFFF - 2 ^ (Received_Lines And 15)
Output_Buffer = "?########"
Chars2Send = 9
CharsRemaining = 9
For j = 0 To 2
Mid(Output_Buffer, 2 + j, 1) = Chr(Asc(Mid(Output_Buffer, 2 + j, 1)) + (31 And Int(lfsr / 32 ^ (2 - j))))
Next j
Debug.Print Output_Buffer
' show what I generated
Received_Lines = Received_Lines + 1 ' keep track of received line count
Else
Input_Buffer = Input_Buffer & Chr(COM_Byte) ' assemble output buffer
' process any characters to send
If CharsRemaining Then
CharsRemaining = CharsRemaining - 1
COM_Byte = Asc(Mid(Output_Buffer, Chars2Send - CharsRemaining, 1))
Put #1, , COM_Byte
End If
End If
End If
DoEvents
Loop
Close
End Sub
This works for me. I'm not sure if the OPEN actually sets up the Baud rate, as I first used TeraTerm.
My COM port is a USB connection to a BASYS3 prototyping kit. It is spewing characters at 9600, records of 36 characters ending with CR. I can randomly send commands of 9 characters. In the above code, I generate these command strings every time I have received a new line.
The way I chose which character to send is a little clunky: perhaps a better way is to have a character pointer and a number of characters, and when those go equal to set them both to zero.
Here is a brief module of VBA code that can send and receive messages on a PC serial port. This is not very elegant, but it is simple and should work on modern versions of Excel and Windows.
You are left on your own to expand the functionality and store or parse the messages. This just shows the low-level stuff to deal with the serial port.
The first 5 lines declare the millisecond "Sleep" library function (based on Excel version).
The SerialPort() subroutine outlines the steps to open the port, transmit some data, receive some data, try again to receive some data (to show that it really does not run afoul of the "end of file" error), and close the port.
#If VBA7 Then ' Excel 2010 or later
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal Milliseconds As LongPtr)
#Else ' Excel 2007 or earlier
Public Declare Sub Sleep Lib "kernel32" (ByVal Milliseconds As Long)
#End If
Public Sub SerialPort()
' open a COM port, transmit a message, gather results, close the port.
' open the COM port as file #1
Debug.Print "Open COM port 4"
Open "COM4:115200,N,8,1" For Binary Access Read Write As #1
transmit$ = Chr(2) + "Hello, World." + Chr(13)
receiveDummy$ = "~~~"
' transmit a message
Put #1, , transmit$
Debug.Print "Message sent."
' wait a bit for a response
Sleep 100
' check for received message
Debug.Print "Look for incoming message."
On Error Resume Next
Do While True
receive$ = receiveDummy$ 'dummy value
Input #1, receive$
If receive$ = receiveDummy$ Then Exit Do 'the string didn't change, so move on
Debug.Print receive$
Loop
On Error GoTo 0
' do it again to show that the empty input queue doesn't stop the flow
Debug.Print "Look again for incoming message (should not stop on error)."
On Error Resume Next
Do While True
receive$ = receiveDummy$ 'dummy value
Input #1, receive$
If receive$ = receiveDummy$ Then Exit Do 'the string didn't change, so move on
Debug.Print receive$
Loop
On Error GoTo 0
' close the serial port
Debug.Print "Close COM port."
Close #1
Debug.Print "Done."
End Sub
Related
I'm busy with software that uses Rs232 Com port communication module.
I send a roll-call out 1st to all users (Ping all users) and then wait 10 min to receive all the roll-calls back, thereafter I send to all the outstanding names (Ping outstanding active individual) using a listview with the remaining members names, I send all the outstanding names in burst of 3's with 3.5 sec in-between. I'm using Threading.Thread.Sleep() to prevent flooding my repeater.
Is there an alternative to Threading.Thread.Sleep() that allows the software to still receive roll-call feedbacks sending the roll-Call pings through the Rs232 Module?
For i As Integer = 0 To LsvCopyRemove.Items.Count - 1
If LsvCopyRemove.Items(i).SubItems(11).Text = "Yes" Then
Dim Group As String = LsvCopyRemove.Items(i).Text
StringData = vbCrLf & "MY String Information comes here"
_rs232.Write("" & vbCrLf)
Threading.Thread.Sleep(500)
_rs232.Write(StringData & vbCrLf) 'The text contained in the txtText will be sent to the serial port as ascii
Threading.Thread.Sleep(3500)
_rs232.Write(StringData & vbCrLf)
Threading.Thread.Sleep(3500)
_rs232.Write(StringData & vbCrLf)
Threading.Thread.Sleep(3500)
Me.rtbTX.AppendText(.Text & vbCr)
'***********************************************************************************************************************************************************************************************************************************************
.Text = String.Empty & vbCrLf
Data = Encoding.Default.GetBytes(.Text)
_rs232.SendData(Data)
'***************************************************************************************************************************************************************************************************************************************************
Threading.Thread.Sleep(6500)
End If
Next
What you are looking for is asynchronous programming, which allows you to execute multiple threads simultaneously. In your example, you'll have to execute the code above which sends the outstanding names in a seperate thread. There are two ways to accomplish this:
Using System.Threading: Put the code you want to execute in a seperate thread in a sub routine, then call it like this:
Dim newThread As New Threading.Thread(AddressOf myFunction)
newThread.Start()
Now the code in the function called myFunction is executed, but the execution of the current function is continued, which allows you to still receive events.
You can also use the newer Threading.Tasks namespace, which can be used in a similar way:
Dim newTask As Task = Task.Factory.StartNew(AddressOf myFunction)
I have an Access database that I'm using to log when items are received. When received, they're scanned in by a unique barcode and I want to log that. The computers running this Access front end are also used for other things, which necessitated the need to use the serial emulation on my scanners (direct/keyboard emulation input isn't an option).
Using Open, I can read from the serial port and all is well unless there is no data waiting to be read--in which case, the Access front end becomes unresponsive for a few seconds. As these computers are used for other tasks, I can't afford to have unresponsiveness.
I've tried using EOF and LOF and both return an error (bad file number) or simply don't function as expected.
How do I check whether information is available to read before attempting Input()?
Edit: let me be more specific. EOF seems to only work for the first read but then always returns true so it skips every attempt to read afterward. LOF always returns bad file name of number.
fNum = FreeFile
Open "COM5" For Input As fNum
...
Do While Not EOF(fNum)
tmp = Input(1, fNum)
If tmp = vbCr or tmp = vbCrLf then
...
Else
myStr = myStr & tmp
End If
Loop
I am developing my first VB program. The program's main task is to send Skype messages using SendKeys.
I managed to send messages successfully but it is kinda slow as I need to use System.Threading.Thread.Sleep(1000) to properly send messages. If I remove the Sleep(1000) function, the program starts to do weird things like partial messages, some dialogs not showing up and at the end Skype crashed and I couldn't type anything.
Problem: How to remove Sleep(1000) to send messages without such large pause? If I have more than 500 contacts it takes lot of time.
My code:
Dim all() As String = System.IO.File.ReadAllLines(appPath)
Dim message_text As String = TextBox1.Text
If all.Length > 0 Then
For i = 0 To all.Length - 1
System.Threading.Thread.Sleep(1000) '' < ---this line
Skype.Client.OpenMessageDialog(all(i))
Skype.Client.Focus()
SendKeys.SendWait(message_text + " - " + DateTime.Now.ToString())
SendKeys.SendWait("{ENTER}")
System.Threading.Thread.Sleep(100)
Next
MessageBox.Show("Ziņa nosūtīta Skype lietotājiem")
Else
End If
I don't understand why it sometimes sends just partial messages as I am using SendKeys.SendWait that should wait till message text is copied in dialog and then Sending "Enter" key.
I have been using folder browser for VBA, I could paste the code of it, but bottom line is that I get returned file name as a string.
Is there any way to access drawing properties (i.e number of layouts) without open?
Public Sub TestFileDialog()
dwgname = FileBrowseOpen("C:", "*", ".dwg", 1) 'dwgname is typeof string
End Sub
Its only the first step (use of FileBrowseOpen function is shown, but also i can use FolderBrowse and collect all .dwg inside of folder),actually i had in mind to batch export all layouts of selected .dwgs to currenty open one. Is there any chance for that?
To effectively read a .dwg file you'll need to open AutoCAD, otherwise the information is not accessible. Some properties may be, such as author, but not number of layouts...
But you can use AutoCAD Console (accoreconsole.exe) to run a headless AutoCAD and use APIs to read any information you need. This is really fast for reading lot's of files and the user will not see it running (but it needs to be installed anyway).
http://aucache.autodesk.com/au2012/sessionsFiles/3338/3323/handout_3338_CP3338-Handout.pdf
you could stay in VBA and use ObjectDBX
it leads to a very similar approach as accoreconsole.exe on in .NET does, i.e you won't see any drawing open in UI since it works on the database itself
It requires adding library reference (Tools->References) to "AutoCAD/ObjectDBX Common XX.Y Type Library", where "XX.Y" is "19.0" for AutoCAD 2014
a minimal functioning code is
Sub main()
Dim myAxDbDoc As AxDbDocument
Dim FullFileName As String
FullFileName = "C:\..\mydrawing.dwg" '<== put here the full name of the file to be opened
Set myAxDbDoc = AxDb_SetDrawing(FullFileName)
MsgBox myAxDbDoc.Layers.Count
End Sub
Function AxDb_SetDrawing(FullFileName As String) As AxDbDocument
Dim DBXDoc As AxDbDocument
Set DBXDoc = Application.GetInterfaceObject("ObjectDBX.AxDbDocument.19") '<== place correct AutoCAD version numeber ("19" works for AutoCAD 2014)
On Error Resume Next
DBXDoc.Open FullFileName
If Err <> 0 Then
MsgBox "Couldn't open" & vbCrLf & vbCrLf & FullFileName, vbOKOnly + vbCritical, "AxDB_SetDrawing"
Else
Set AxDb_SetDrawing = DBXDoc
End If
On Error GoTo 0
End Function
Still, you must have one AutoCAD session running from which make this sub run! But you should have it since talked about "currently open" drawing
There are two serialports in my computer.
How do I get the serialport information?
(I know about coding of Serialport, but I really want to know about Serialport of my Computer information)
is it possible to get the information about Serialport of my computer?
I'm coding in vb.net
is is possible in vb.net?
if it possible, please tell me how to do it.
which information do you want to have about the serial port?
using the MSComm control you can use the following function to determine if the port exists and if it's already in use or not:
Public Enum PortAttr
PortFree = 0
PortInUse = 1
PortUnknown = 2
End Enum
Public Function CheckPort(intPort As Integer) As PortAttr
On Error GoTo ErrorFound
With MSComm1
If .PortOpen Then .PortOpen = False
.CommPort = intPort
.PortOpen = True
CheckPort = PortFree
If .PortOpen = False Then .PortOpen = True
End With 'MSComm1
Exit Function
ErrorFound:
Select Case Err.Number
Case 8002 'port doesnt exist
CheckPort = PortUnknown
Case 8005 'port already in use
CheckPort = PortInUse
Case Else
MsgBox Err.Description, vbCritical, "Error " & CStr(Err.Number) & " on Port " & CStr(intPort)
End Select
On Error GoTo 0
End Function
You can then loop from 1 to 16 to see if any of these ports exist (usb converters could add extra ports)
For intIndex = 1 To 16
Select Case CheckPort(intIndex)
Case PortFree
intFree = intFree + 1
cboPort.AddItem "Com" & CStr(intIndex), intFree 'add the port to the "free" list
cboPort.ItemData(intFree) = intIndex
Case PortInUse
cboPort.AddItem "Com" & CStr(intIndex) 'add the port to the "in use" ist
End Select
Next intIndex
Use the MSComm control in Visual Basic .NET to access serial ports
Because no Microsoft .NET Framework classes exist to access the communications resources that connected to your computer, you can use the MSComm control in Microsoft Visual Basic 6.0. The MSComm control provides serial communications for your application by enabling the transmission and reception of data through a serial port. To implement basic serial communications by using a modem, follow these steps:
Start Microsoft Visual Studio .NET.
On the File menu, point to New, and then click Project.
Under Project Types, click Visual Basic Projects.
Under Templates, click Console Application.
In the Name box, type MyConsoleApplication, and then click OK.
By default, Module1.vb is created.
Right-click the MyConsoleApplication project, and then click Add Reference.
Click the COM tab, click Microsoft Comm Control 6.0 under Component Name, click Select, and then click OK.
Note To use the MSComm control, you must install the related COM components of Microsoft Visual Basic 6.0 on the same computer that has Microsoft Visual Studio .NET installed.
For more information about license issues when you use Visual Basic 6.0 controls in Visual Studio .NET, click the following article number to view the article in the Microsoft Knowledge Base:
318597 Errors when you use Visual Basic 6.0 controls in Visual Studio .NET
Replace the code in Module1.vb with the following code example.
Imports MSCommLib
Module Module1
Sub Main()
'New a MSComm control
Dim MSComm1 As MSComm
MSComm1 = New MSComm
' Buffer to hold input string.
Dim Buffer As String
' Use the COM1 serial port.
MSComm1.CommPort = 1
' 9600 baud, no parity, 8 data, and 1 stop bit.
MSComm1.Settings = "9600,N,8,1"
' Tell the control to read the whole buffer when Input is used.
MSComm1.InputLen = 0
' Open the serial port.
MSComm1.PortOpen = True
Console.WriteLine("Open the serial port.")
' Tell the control to make the Input property return text data.
MSComm1.InputMode() = InputModeConstants.comInputModeText
'Clear the receive buffer.
MSComm1.InBufferCount() = 0
' Send the attention command to the modem.
MSComm1.Output = "ATV1Q0" & Chr(13)
Console.WriteLine("Send the attention command to the modem.")
Console.WriteLine("Wait for the data to come back to the serial port...")
' Make sure that the modem responds with "OK".
' Wait for the data to come back to the serial port.
Do
Buffer = Buffer & MSComm1.Input
Loop Until InStr(Buffer, "OK" & vbCrLf)
' Read the "OK" response data in the serial port.
' Close the serial port.
Console.WriteLine("Read the OK response data in the serial port.")
MSComm1.PortOpen = False
Console.WriteLine("Close the serial port.")
End Sub
End Module
Press CRTL+F5 to build and run this project. You will receive the following output messages:
Open the serial port.
Send the attention command to the modem.
Wait for data to come back to the serial port...
Read the OK response data in the serial port.
Close the serial port.
To read the rest of this you can go here How to access serial and parallel ports by using Visual Basic .NET