RE:power builder 9 y AT caller id 1.0 OCX
We begin this article by asking the question, why would you want to add caller ID to your PowerBuilder applications anyway? I can think of about a handful of applications for it, mostly revolving around picking a phone number and querying a database. For example, "Good morning, Chance, will you be ordering the cheese and sausage pizza again?" Or perhaps a database of people, some whom you might not like, just to have your modem hang up on them. The possibilities are endless.
First some introductions. How does caller ID information get relayed from Ma Bell to your phone or modem anyway? Modems use a technique called Frequency Shift Keying to transmit information over a phone line. One tone represents a binary 1, while a nother represents a binary zero. A modem changes frequencies depending on whether it wants to send a 1 or a 0. To send caller ID information to your home it simply sends ASCII data to the caller ID box between the first and second rings. The modem then decodes the bits and makes the data available for your viewing pleasure. Incidentally, there's a more advanced system that contains the caller's name and the date and time of call but its technique is identical.
In this demonstration we'll simply pick off the incoming phone number (but you could use a similar technique to get the caller's name if you want). However, before we begin you'll need a few things:
* A telephone account with the caller ID feature enabled
* A modem capable of reading caller ID information
* Some time to learn
For starters, we'll need a way to communicate with the modem. In general, modems are serial devices so we'll need a way to "talk to" the comm port. We'll need to use the Win32 API rather than the native PowerBuilder file I/O functions. Why? Because PowerBuilder buffers data on top of that already being done by the operating system. That can be problematic for serial devices. With that, here are the external function prototypes that you'll need for your project.
Function Long CreateFile(ref string lpszName, long fdwAccess, long &
fdwShareMode, long lpsa, long fdwCreate, long fdwAttrsAndFlags, long &
hTemplateFile) Library "Kernel32.dll" ALIAS FOR "CreateFileA;Ansi"
Function Long ReadFile(Long hFile, ref string lpBuffer, long nBytesToRead, &
ref Long nBytesRead, Long lNull) Library "Kernel32.dll" alias for "ReadFile;Ansi"
Function Long WriteFile(Long hFile, ref string lpBuffer, long nBytesToWrite, &
ref Long nBytesWritten, LONG lNull) Library &
"Kernel32.dll" alias for "WriteFile;Ansi"
Function Long CloseHandle(Long hObject) Library "Kernel32.dll"
For demonstrations purposes, we'll have to create a GUI capable of displaying the incoming information. You might use something like this: (Figure 1)
With that in place, we can go about coding our main algorithm in a "pre-open" event. It looks like this:
If wf_open_modem() Then
The first function tries to open the comm port the modem is located on. It looks like this. (Note: I've hard-coded the port number in. In an actual implementation you'd probably want this to be a user-defined setting.)
//poll device until we get a hit
Integer li_sub = 1
Boolean lb_connected = FALSE
UnsignedLong ll_tmp = 3221225472
is_CommPort = "COM3:"
//modem already detected
If il_hCon > 0 Then
//try to connect
Do While li_sub <= 10 And Not lb_connected
il_hCon = CreateFile(is_CommPort, ll_tmp, 3, 0, 3, 0, 0);
If il_hCon < 0 Then
MessageBox(This.Title, "Modem NOT detected " + String(il_hCon))
MessageBox(This.Title, "Modem detected " + String(il_hCon))
lb_connected = TRUE
If Not lb_connected Then
MessageBox(This.Title, "Modem not detected ")
A little explanation may be in order with the call to CreateFile. You may be wondering where the value 3221225472 comes from and what it represents. If we look at the prototype for the function, we'll see that this is the access mode to the commport that can be read, write, or both. In our case, we are interested in the hexadecimal constants GENERIC_READ and GENERIC_WRITE or more precisely these two values or'd together. For example, in hex, 0x80000000 | 0x40000000 = 0xC0000000. Convert that to a decimal and we have our constant 3221225472.
How did I know the values of the hexadecimal constants to begin with? There are many sources but one I find useful is a tool called ApiViewer. It's invaluable when it comes to prototyping Win32 API calls. A link to it can be found at the bottom in the references section.
A further look at the prototype for CreateFile sheds some light onto the other constants, in particular 3, 0, 3. Again, if we look at the prototype for CreateFile, we see that these represent the ShareMode, SecurityAttributes, and CreateDisposition, respectively. For the ShareMode we are interested in FILE_SHARE_READ | FILE_SHARE_WRITE. Again, look at APIViewer to find out their hex values, or them together and convert to yield decimal 3. For the SecurityAttributes, this represents a pointer to a SECURITY_ATTRIBUTES structure that determines whether or not the returned handle can be inherited by child processes. If lpSecurityAttributes is NULL, the handle cannot be inherited. This is what is desired in our case, thus the zero. Finally, a word about the CreateDisposition, which is an action to take on files that do and do not exist. With respect to a comport, we expect it to exist. Therefore, the value OPEN_EXISTING is in order. Again, use the ApiViewer tool to see the numeric values of this constant.
Back to our project, the next order of business is to set the modem up to capture caller-ID information. The command for doing this may vary depending on your manufacture; consult your modem documentation. I was using a Toshiba software modem. With respect to the call to WriteFile, ls_write is the text to send to the modem, ll_write is the length of the text sent to the modem, and ll_written is the length actually written to the modem.
//turn on caller-ID
ls_write = "AT#CID=1" + Char(13)
ll_write = Len(ls_write)
ll_rc = WriteFile( il_hcon, ls_write, ll_write, ll_written, Long(0) )
Now all we have to do is wait for an incoming phone call and pick off the data. A couple of points are important here. One, the string that indicates the information is the phone number can vary depending on your particular modem. Again, experiment or consult your modem documentation. The second thing is that the data is returned with carriage returns and line feeds. While nice to look at, it can make parsing more difficult. Therefore, I've replaced them with spaces. With respect to the call to ReadFile, it follows the same logic as WriteFile(see above). Here is what the function to pick off the caller id looks like:
do while not Match(ls_result, 'NMBR')
ls_read = SPACE(100)
ll_rc = ReadFile( il_hcon, ls_read, len(ls_read), ll_read, Long(0) )
//do some parsing
For ll_i = 1 to Len(Trim(ls_read))
ls_Char = Mid(ls_read, ll_i, 1)
If ls_Char = Char(10) Or ls_Char = Char(13) Then
ls_Result += ' '
ls_Result += ls_Char
//here we got our string so pick off number
ls_Number = Mid(ls_Result, Pos(ls_Result, 'NMBR') + 5, 10)
sle_number.Text = ls_Number
Finally, in the close event we will need to make sure we close the open comm port.
That's it! Incredibly simple but powerful coupled with a database of phone numbers you can query to personalize your applications for your end users.