Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

VFP and IP Server 1

Status
Not open for further replies.

David Higgs

Programmer
May 6, 2012
390
GB
Basically my VFP Application is the Front-End to a third party piece of software using the same My-SQL Database.

I have a "Radio Communications Transceiver" connected to (Serial Port) and controlled by the third party software. I would like my VFP application to also control the "Radio Communications Transceiver" using an API / IP Server.

I have been using a program called "Packet Sender" to establish communication with the third party piece of software by sending specific arrays of bytes to it's IP Server (Address 127.0.0.1 / Port 7809 / Protocol UTF16 characters).
The application will typically send the following array of bytes for the command "Get Radio" :-

Hex
28 00 00 00 cd ab 34 12 34 12 cd ab 00 00 00 00 47 00 65 00 74 00 20 00 52 00 61 00 64 00 69 00 6f 00 00 00 00 00 00 00

Ascii

(\00\00\00\cd\ab4\124\12\cd\ab\00\00\00\00G\00e\00t\00 \00R\00a\00d
\00i\00o\00\00\00\00\00\00\00

The response to the above is sent back in a similar format.

So, basically I am looking for some guidance of how to proceed as I've not used VFP and IP Protocol before.


Regards,

David.

Recreational user of VFP.
 
I don't see reading all of it as the easiest way to pick up what you need.
In your case you have a constraint by a hardware needing some non http protocol, so the argument of using http instead of something proprietary doesn't fit in here. The procotol to this device will stay what it is. So here winsock communication would be one solution.

1. specifying the message in VFP code:
Code:
qGetRadio= 0h28000000cdab34123412cdab00000000470065007400200052006100640069006f00000000000000

2. using Winsock is shown as mentioned in the other thread in Since your device is the "server" your code just acts as the client part, you don't need the server side listening socket, you only need the client side requesting socket receiving answers from the device.

Code:
#Define EOT  Chr(4) && End of Transmission sign

Local lqGetRadio, loSender

lqGetRadio = 0h28000000cdab34123412cdab00000000470065007400200052006100640069006f00000000000000
loSender = Createobject('Sender',7809,'127.0.0.l')
IF loSender.lConnected
   loSender.sendcommand(lqGetRadio)
ENDIF
*received response is printed on screen
ON KEY LABEL F5 CLEAR EVENTS
READ EVENTS

Define Class Sender As Form
   nProtocol  = 0
   nPort      = 0
   cRMHost    = ""
   nStat      = 0
   lConnected = .F.

   Procedure Load()
      Sys(2333,0)
      _vfp.AutoYield = .F.
   Endproc

   Procedure Unload()
      _vfp.AutoYield = .T.
      This.oSock.Close()
      Do While This.oSock.state > 0
         DoEvents Force
      Enddo
      This.lConnected = .F.
   Endproc

   Procedure Init
      Lparameters tnPort, tcRemoteHost

      This.nPort   = tnPort
      This.cRMHost = tcRemoteHost
      This.AddObject('oSock', 'oleSock')
      
      With This.oSock
         .protocol = 0  && TCP
         .remoteHost = Alltrim(Thisform.cRMHost)
         && RemoteHostname
         .remotePort = Thisform.nPort
         && RemotePort
         .localPort = 0   && Takes any available LocalPort
         .Object.Connect()
         Do While .Object.state < 7
            DoEvents Force  && we must check for the State
            If .state = 9
               Exit
            Endif
         Enddo
         If .Object.state = 7
            This.lConnected = .T.
         Else   && we Couldn't connect :-(
            This.lConnected = .F.
         Endif
      Endwith
   ENDPROC
   
   PROCEDURE Send()
      LPARAMETERS tcMessage
      With This.oSock
         If .state = 7
            && No connection, no send
            .SendData(tcMessage+EOT)
            ? "result: 0h"&& new line for showing received data in dataarrival
         Else
            .Error( -1, 'Connection lost', , , , , .T. )
            .Close()
            This.lConnected = .F.
         Endif
      Endwith
Enddefine

* -------- WinSock ActiveX
Define Class  oleSock As OleControl
   OleClass = "MSWinsock.Winsock"

   && a buffer to stitch incoming transmission together
   Procedure Error
      Lparameters Number, Description, Scode, Source, Helpfile, HelpContext, Canceldisplay
      * This method is used only for error diplaying purposes.
      ? "Error ---- " + Str(Number) +'  - ' + Description + Chr(13) + Chr(10)
   Endproc
   Procedure Close()
      * This is necessary to really close the socket, without it you will end up
      * in a timeout
      This.Object.Close()
   Endproc

   Procedure Destroy
      * It's used to close the socket if the user close the form
      This.Object.Close()
   Endproc

   Procedure DataArrival
      Lparameters tnByteCount
      Local lcBuffer
      lcBuffer = Space(tnByteCount)

      * This gets the data from the socket.  It happens, that the data
      * isn't received in a single rush. Thus we use a EOT (end of transmission)
      * sign to be sure, the data is complete. Until we get this, the data is stuffed
      *  into cRecieveBuffer.

      This.GetData( @lcBuffer, , tnByteCount )
      If At( EOT, lcBuffer ) = 0  && CHR(4) not found, not yet finished
         ?? TRANSFORM(CREATEBINARY(lcBuffer))
      Else
         ?? TRANSFORM(CREATEBINARY(Left(lcBuffer, At(EOT, lcBuffer)-1)))
      Endif
      Return
   Endproc
Enddefine
To end the program running press F5.

Your device might not like to receive EOT (chr(4)) nor will send it back, so if that fails perhaps just define EOT as the empty string for a second try, the DataArrival might always get the short responses the device sends back in a single packet, so there is no need for an end of transmission signal byte. Doesn't matter much, as the code also doesn't depend on EOT being sent or received, it'll just display arrived bytes as they come via ??

You want to change this to collect the arrived data and finally read it out, the details of that are beyond me not having your device, but this code should work out as starting point.

Bye, Olaf.
 
And if that doesn't work at all, the software "Packet Sender" might have acted as server recieving ip packets and send them to COM1, then to circumvent that and remove the need for that software you don't need to act as winsock client nor server, but you need to use MSCOMM32.ocx:


Bye, Olaf.
 
Nigel Gomm said:
discussion and examples in this thread may be of interest
Nigel, Thank you for the link, I will study at my leisure!

Regards,

David.

Recreational user of VFP.
 
Olaf,

Many thanks for your input, much appreciated.

I've carried out a few tests using the code you supplied.

When the Code is first run, the following errors occur:-
Error 11001 - Authoritative Answer: Hostnot found
Error 232 - Sender.osock.Error

The "Host not found" error was traced to the last character in the IP address being a lower case 'L' instead of number '1'.

Having changed the above, the following error now occurs:-
Property SENDCOMMAND is not found.

Changing the 'sendcommand' to 'osock.SendData' in the following lines, the program sent a request and recieved a reply.

IF loSender.lConnected
* loSender.sendcommand(lqGetRadio)
loSender.osock.SendData(lqGetRadio)
ENDIF

The only other known issue that I've yet to resolve is in the "Unload Procedure" where I get a 'Unknown Member OSOCK' error when pressing F5 (Exiting the program).




Regards,

David.

Recreational user of VFP.
 
David said:
Having changed the above, the following error now occurs:-
Property SENDCOMMAND is not found.

Changing the 'sendcommand' to 'osock.SendData' in the following lines

Well, instead of calling loSender.SendCommand(lqGetRadio) you just need to delete "command" and call loSender.Send(lqGetRadio), the Sender class has a Send() method, which has the necessary code for calling into oSock.SendData(). If you stay with your code for more direct oleSock usage, remove the Send() method. Another simple option is renaming that Send() method to SendCommand().

The other errors are surely somewhat creeping in through shortening the wiki code and renaming things without the ability to test without the device at hand. I see you can cope with those errors and fix the situation quite on your own. It was, as said, meant as a starting point.

In regard of the missing oSock in the form unload, I'd say that's because the oSock isn't a predefined object, but added with AddObject in the forms Init, then unloaded BEFORE the forms Unload event occurs. You can simply skip that line, as the oleSock class definition does the Close() call in its own internal Destroy() event anyway, so the sender Unload would only need to set the _vfp.Autoyield=.T., and even that is optional, though recommended. Ideally, the autoyield should always be .t., but the Wiki code turned that off for good reasons while using Winsock, I assume.

And by the way: The reason the whole class is conceptually a form, though it's never shown is, the Winsock control only works instanciated on a form, it can't be used standalone, that's some licensing issue. So don't try to shrink down the code further. It also means the form itself is your adapter/wrapper around the Winsock and though it seems more appropriate to call oSock.SendData() the outer code should only act on the sender form itself, not on the oSock child object for reasons of adhering to OOP encapsulation and the adapter pattern, not for technical reasons.

Bye, Olaf.
 
Once again Olaf, many thanks for your help. I didn't expect I would get this far so quickly!

I took your advice and renamed the "Send()" method to "SendCommand()". I also made the alterations to "Procedure Unload", all now working as expected.

Obviously I have some work to do before I can integrate this PRG into my application. I have to write the "Hex Command List" and also decide how to decode the "Hex reply" etc. I am thinking that I will pass a "Command String" in Hex to the PRG and return with the "Reply String". When a "connection" is first established you have to send a "Get Context" Command and use the "Context Value" returned in addition to any subsequent Commands. The Context remains remains valid as long as the IP Server is connected. One thing that I am unsure about is if I need to "Unload" the Form in an orderly way when the PRG as finished it's process, if so, what's the best way of doing this?


Regards,

David.

Recreational user of VFP.
 
You're welcome.

In regard of decoding: I see what you mean, but one detail perhaps, the bytes coming back are as you know them, I just turn them to hex for display purposes, if you change DataArrival this way you see the usual "gibberish":

Code:
      This.GetData( @lcBuffer, , tnByteCount )
      If At( EOT, lcBuffer ) = 0  && CHR(4) not found, not yet finished
         ?? lcBuffer
      Else
         ?? Left(lcBuffer, At(EOT, lcBuffer)-1)
      Endif

Instead of ?? you'll need to add this to some property like cResponse:

Code:
      This.GetData( @lcBuffer, , tnByteCount )
      If At( EOT, lcBuffer ) = 0  && CHR(4) not found, not yet finished
         This.cResponse = This.cResponse + lcBuffer
      Else
         This.cResponse = This.cResponse + Left(lcBuffer, At(EOT, lcBuffer)-1)
         This.lFinished = .T.
      Endif
Which obviously needs two new properties cResponse and lFinished of the oleSock class you set in SendData() to empty string and .F.

David said:
if I need to "Unload" the Form in an orderly way when the PRG has finished its process, if so, what's the best way of doing this?

You can use the loSender for multiple commands, as soon as loSender is released the class including Winsock will also release and close the connection, there's nothing to do there. But if you like to ensure, as loSender actually is a form, you can call loSender.Release() to release the form before the loSender variable is released by quitting the PRG.

The remaining task is a bit tricky, as I don't know the response behavior in timing. Maybe you always get your response in a single DataArrival, maybe not. You can figure it out. Depending on that you could extend the SendCommand() method to return with the cResponse property value by waiting after the SendData() call. If you receive EOT from the device the oleSock could also signal to be finished with data received as above and SendCommand would wait for lFinished being set .T.

Last not least, the whole EOT thing could be counterproductive, if CHR(4) can be a natural part of the device's response, which is likely the case if the response is some binary information. The best way then would be a timeout, unless you have ways to know the response lengths to be expected or any other sign of completed return message.

Bye, Olaf.
 


Here is a little more about the Protocol in use. As the Program is just reading and writing commands to a Transceiver the Data Length is typically 100 Bytes. The maximum I've seen is around 500 Bytes.

Code:
CLEAR

DO Rx_Context

*	 The protocol for the API looks like this:
* 	      All data characters are 2 - Bytes per "character".
* 	  (a) 4 Bytes - Contains the length of the entire message.  Least Significant Byte First.
*	  (b) 8 Bytes - Signature [cd ab 34 12 34 12 cd ab] - This is always the same.
*	  (c) 4 Bytes - Always 0 [0 0 0 0]
*	  (d)   Payload Data (UTF16/Unicode): Your message or the servers response
*	  (e) 6 Bytes - Always 0 [0 0 0 0 0 0]
*
*	Fixed Bytes = 22 Bytes

	  * Length = 4 (length) + 8 (signature) + payload length + 6



PROCEDURE Rx_Context	&& Receive and Decode Context Response	  
*
*	GET Context - Response String
*
Rx_Context = "1A000000CDAB34123412CDAB0000000033003900000000000000" 		&& Context Responce
*             < (a)  ><   Signature  >< (c)  >< (d)  ><   (e)    >

Len_Rx_Context = "0x" + SUBSTR(Rx_Context,3,2) + LEFT(Rx_Context,2)			&& Length of String in Hex
Len_Rx_Data = ((&Len_Rx_Context) - 22)*2	&&	Total Fixed Bytes = 22 x 2 - Bytes per "character"

&&	Extract Data from end of Signature + (c) 4 Bytes of Zeros and (e) Remove 6 Bytes of Zeros from end of string
cContext = LEFT(STREXTRACT(Rx_Context,"CDAB34123412CDAB00000000"),Len_Rx_Data)	

context_string = "5b00" + cContext + "5d00"	&& In this UTF16 to Ascii example Context = [39] 

? "Context String = ",context_string 

ENDPROC

As you can see there are some 22 Double Byte Fixed characters that once stripped will give, for example the "Frequency Readout" in Hex Double Characters. So the surplus Zero's need to be removed and then the remaining Data needs to be converted to readable ascii characters. There are not that many Commands to send / receive and some of those will be of the "on / off" fixed string variety.

This is the Response to a "Get Frequency" command:-
26 00 00 00 CD AB 34 12 34 12 CD AB 00 00 00 00 31 00 34 00 32 00 33 00 35 00 31 00 30 00 38 00 00 00 00 00 00 00
The Frequency when decoded should read 14235108Hz (14.235.108 Mhz). The



Regards,

David.

Recreational user of VFP.
 
Well, there you have it:
>(a) 4 Bytes - Contains the length of the entire message. Least Significant Byte First
If all responses begin like that you have an expected length and can stop DataArrival after this number of bytes is received.

You can specialize the DataArrival event of your oleSock to make use of that detail knowledge. If cResponse is 4 bytes at minimum you can already decode the expected response length.
>(b) 8 Bytes - Signature [cd ab 34 12 34 12 cd ab] - This is always the same.
If these 8 bytes are not coming back you know something went wrong, you end receiving and return an error or set an error state.
>(c) 4 Bytes - Always 0 [0 0 0 0]
Dito, you can count that into (b), too
Etc.

If not all responses are of the same type you can also specialize the SendCommand or SendData to set the family or group of responses and prepare some info for DataArrival to know what to expect.

Bye, Olaf.




 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top