HOME - - - - - Delphi Tutorials TOC - - - - - - Other material for programmers
Delicious  Bookmark this on Delicious    Recommend to StumbleUpon

TCP/IP programming with Delphi.
Getting started- fundamental principles.
An example connecting with a TEMPerLAN server.

This has good information, and a search button at the bottom of the page

Please don't dismiss it because it isn't full of graphics, scripts, cookies, etc!

Click here if you want to know more about the source and format of these pages.

This page is "browser friendly". Make your browser window as wide as you want it. The text will flow nicely for you. It is easier to read in a narrow window. With most browsers, pressing plus, minus or zero while the control key (ctrl) is held down will change the texts size. (Enlarge, reduce, restore to default, respectively.) (This is more fully explained, and there's another tip, at my Power Browsing page.)


First a note to non-programmers who have come to this page

Most of this page is about writing programs. If you came here to collect a program for reading from the PCSensor.com TEMPerLAN, fear not! Just download the TCP001 zip file. In it you should find TCP001.exe. That's all you need! Save that. It is the "finished program", not a setup file. Run it, fill in the "port" (probably 5200 will be right for you) and "server" information, and all should be well. (You put your TEMPerLAN's IP address in the "server" box.)

The .zip file also has a file called TCP002.exe. That is a better application... it is an enhanced TCP001. TCP002 is for people who have a TEMPerLAN, and want to be able to access it across the web, or want to have readings taken at a different interval than the once- per- second which is all the software supplied with the TEMPerLAN in September 2011, as far as I could see.

The .zip file also has an application called TCP003.exe. This is a crude simulator of a TEMPerLAN with three sensors. The first two always report 16.06 degrees C and 31.87 degrees C. The third sensor reports temperatures from 16.06 degrees C to 31.87 degrees C, and the temperature reported changes once per second. It goes up by one sixteenth of a degree Celsius. After the temperature reaches 31.87, it drops back to 16.06 again.

The TEMPerLAN simulator was developed from the ICS demo "TcpSrv". (Not to be confused with "SrvTcp"!) It isn't 100% reliable, but seems to work, mostly! (If you are having trouble when using the TEMPerLAN simulator (TCP003) to run through the tutorial, start shutting down both server (TCP003) and client each time you revise the client. Restart the server before you restart the client.(They can both be on the same machine.)



Getting started with TCP/IP programming

A modest example of a TCP/IP client application

At first glance, this might seem to be a tutorial of little interest even to the admittedly small universe of programmers interested in TCP/IP. It talks a great deal about communicating with a TEMPerLAN, which few of you will have.

Fear not! While you need a TEMPerLAN, or Arduino (or other) clone... that's something I hope I can get arranged, by the way... to actually do the exercises here, the topics covered are of enormous general significance to anyone trying to work with TCP/IP... some of the most fundamental issues are explored. You won't be able to work with web page servers (be it making servers or clients), won't be able to do email programming, etc, etc, without a grasp of the issues covered here, which are part of the foundations of those more exalted activities. By the way: not every server is serving HTML.

For the sake of a pretty picture, here's what the application developed in this tutorial looks like, when reading (across the LAN) from a TEMPerLAN with three temperature sensors attached. It was reading at one reading every two seconds. Can you see which sensor I had just grasped in a warm hand? Not how the rise decelerates as the temperature of the sensor approaches that of the hand.

Screenshot of program TCP001 in action

A compiled .exe file for that can be downloaded. It is part of the .zip file which contains all of the sourcecode for the application. It was written using Delph7 and the Internet Component Suite (ICS) by Francois Piette and associates.

Some things to keep in mind as you work through this material...

The software it describes makes a PC a client for the TEMPerLAN (or equivalent). Putting that another way: we are going to write client software. And the TEMPerLAN, as you may have guessed, is a server in all of what follows.

There is nothing set in stone about what a client sends to a server, nor in whether it responds at all, nor in how it responds, when it does, when you are working with IP/TCP at this level. I will give you the details of what the TEMPerLAN will respond to. (That dictates what the client should send!) And I will tell you how the server responds when it responds... which determines how you write the "display the answer" parts of the program. But all of that is "detail"; it is all just a distraction. Necessary distraction, yes... but don't let it distract you from the basic principles being illustrated here.

Once you have mastered those basic principles, you can go on to working with web servers, and other client/ server situations. In those situations, certain rules do apply about what is sent by the client, how the server responds. But until you can do a basic, "free form" send, until you can read SOMETHING coming back, you aren't ready for the next issues, are you? This study of the TEMPerLAN will get you there!

The TEMPerLAN is a neat little device from PCSensor.com. You plug 1-Wire temperature sensors, which are Good People, into one side, and then you plug the other side into your LAN, and Presto!, you can see the temperatures the sensors are seeing. That's the theory.

As I said, even if you don't have a TEMPerLAN, the ideas in this discussion of accessing a TEMPerLAN may be useful. I can think of plenty of similar devices you could create with an Arduino with an ethernet shield, for instance. (Cost of hardware? As low as $40.) A LAN interface for the PCSensor 1-WireRelay/I-O device is needed, for a start.



Many things which you attach to LANs have all sorts of clever programming in them. The TEMPerLAN eschews this complexity.

The device watches for two hex bytes to arrive. If it sees "BB 82", it replies with a string like the following....

BB 82 03 01 F4 01 6C 01 61 FF

The first two bytes are always BB 82

The 03 in the example tells me that there were three temperature sensors attached to the TEMPerLAN which was being read.

The next six bytes were the temperatures seen. More on this in a moment.

The final byte of a valid reply will always be FF

Let me, once more, go back to my claim that this tutorial is of general importance.

In order to "finish what we've begun", towards the end of this essay, I show you how to make use of the string returned by the TEMPerLAN. Those details are not of general significance. But devices that send SOMETHING in certain triggering circumstances are common, and you might well make one or two of your own devising. And, whatever gets sent, dealing with it is really a trivial issue when you consider it beside the greater, and more generally useful issues of getting it from the server to the client.

So enjoy using a TEMPerLAN with this software, if you happen to have one... But those of you who don't have one don't need to stop reading yet! (The TEMPerLAN can respond to other commands, as well, but this essay concentrates on BB 82).


The payload

So... how is "01 F4 01 6C 01 61" three temperatures? (We'll come to how to send the BB 82 and fetch the reply in a moment)

First, split it up thus...

01 F4: first temperature
01 6C: second temperature
01 61: third temperature

(Which sensor feeds "first", which "second", etc, is more or less arbitrary... but it will be constant for any given set of sensors, by the way.)

"01 F4" is the raw "temperature" as returned by the Dallas sensor, a DS18B20, if you want to explore the datasheet. It is a 16-bit, signed, 2s complement number, expressing the temperature in 16ths of a degree Celsius. Don't panic Mr. Mannering, I will be dealing with the consequences of that for you. I only report the detail for my fellow obsessives, and to "show my workings", to help you if there are mistakes in this. (Surely not! But please write and inform me of anything you think is a mistake... That will help me look good if you are right, and help me tailor the text if you are wrong... you can save then next reader perplexity.) You are given the most significant byte first. Thus, if the sensor returned 0001, the temperature it is seeing is 1/16th degree Celsius.

Changing the temperatures from sixteenths of degrees into hundredths of degrees isn't as hard as you might think. The details will be in the code, if you want them. The hard bits, for me anyway, were sending the BB 82 and reading the answer.

Well... it was hard to pin down what I had to do. And hard to find the right tree in the forest which is the superb Internet Component Suite from Francois Piette and the huge and generous ICS community. The ICS gives Delphi and C++ Builder programmers the components to build client/ server applications, and the basic elements of many other related applications.

It turned out that the ICS Client5 tutorial was about all I needed. What follows is an account of tweaking that to create "my" application for reading the TEMPerLAN.

The following is a greatly condensed version of my path to success, but I will try to head you off from as many of the dead ends I explored as I can remember.

To exactly follow this, you need a TEMPerLAN, or SOMETHING that behaves as it does... something sitting on your LAN which responds when you send some bytes to it.

The first big breakthrough for me was being told about the marvelous, free "SmartSniff", a packet sniffer. It shows you what's bouncing around you network. It is a little scary... you wouldn't believe all that's going on. It, like any powerful tool, takes a little getting used to, a little mastering, but I will be helping you there.

You don't have to "install" it. (Unless you want to... you can get a version that gives you a nice "uninstall" button.) I used the option I prefer: I downloaded a nice simple zip with a single nice simple .exe in it. I put the .exe someplace sensible. I put a shortcut to it someplace sensible. And I was done. (Mostly).

I say "mostly", because one of the experts helping me said that because I was using Windows, I really ought also to install WinPCap too... without it, SmartSniff was going to miss some stuff. I think for my simple needs, it wasn't actually necessary, but if you are going to set up a sniffer, you might was well set up a comprehensive one. While the SmartSniff "setup" was a breeze, and the sort of Nice Simple Software I like, WinPCap was a little more scary. As with anti-malware software, it does, of course, need to be installed deep in the bowels of your OS, and thus is never going to be "simple". And never going to be "safe"... but I wanted what it offered, and so proceeded.

It isn't so much an application as a set of drivers. When you have installed it, your Control Panel "Add Remove Programs" lists gains an entry "WinPCap". And when you start SmartSniff... sometimes... it asks if you want to use WinPCap, or just "raw sockets". (That dialog is one that it is easy to skip over with glazed eyes, because the question in it, to a newbie like me, anyway, seems abstruse, and it is easy to just click "okay", hoping for the best!) So! You need to install WinPCap, and you have to tell SmartSniff to use it!

SmartSniff

Yes, but what does SmartSniff do, I hear you wail....

Neat stuff!

But what it is doing, and what you think it is doing may not be the same thing!

Fire it up. Then use your browser to look at something, or even just refresh this page.

The SmartSniff tool has two panel. At the top is one filled with a line per detected TCP/IP connection.

Click on any one of those lines, and in the bottom panel you will get a display of the traffic across that connection up to the time when you changed the line you have selected in the top panel. N.B.: You need to change away from "connection A" to a different one and then back to "A" to refresh what's shown in the list of traffic.

You'll want to use the restart capture button (Keyboard shortcut: ctrl-R) from time to time, just to "weed the garden". Don't be alarmed by all that is going on. You may have a major virus infestation, but a lot of activity in SmartSniff's windows doesn't necessarily mean that you do!

So! You've got SmartSniff running. You've refreshed something you have up in your browser. Click on a few of the lines in SmartSniff's top panel, ones for protocol TCP, remote address something not starting 192, remote port 80, and eventually, you should stumble upon something vaguely like....

GET /js_ libs/js- lib_v2.js HTTP/1
.1..Host : www.overbyte.be..User-
Agent: Mozilla/5....

That's what I got just now when I refreshed a page from Overbyte.be, using Firefox.

You are seeing the "raw stuff" that your browser sent to the page you refreshed, and if you scroll down the page, you will see the "raw stuff" your browser displays so nicely for you. You have "tapped the line" between your browser and the internet.

Now a moment ago, I said "don't be frightened" when you start digging with SmartSniff. Not about all the traffic. Now I will show you something that SHOULD frighten you.

Just before you do the next thing, do ctrl-r in your SmartSniff window, on the first PC.

Use the PC to, say, fetch some email.

Depending on how you do email, you may find details of your log on password, etc, in the SmartSniff record of your "conversation" with your mail server. I wasn't able, this morning, to see such things from a conversation on a second PC on the LAN... but even if that's not possible, it wouldn't be hard to leave SmartSniff running on a PC in a minimized state. And it is certainly possible to do SmartSniff type things with a blatantly malevolent program that is deliberately written to run "silently". And then there are keyloggers, of course. Yikes. Be careful out there, people.

Anyway... back to less exciting things...

So... you've got SmartSniff installed. Be sure to acquaint yourself will ALL the columns in the top panel... you'll have to use horizontal scrolling, column resizing, etc. You can save your preferred configuration with "File | Save Config..."

For each connection, in the bottom panel you can see a history of communications. That history includes both outgoing messages and incoming messages for the connection in question. You can watch the figures in the "data size" column to see which channels are active, and to see when something arrives.

Use "Options | Display mode" to get the results that are best for your needs. (Use "Hex dump" if in doubt... that will show you the data both ways... as (hex) numbers, and, at the right, characters when the datum has an assigned ASCII character, or a dot as a placeholder when it doesn't.

We are at last ready to start with the ICS demo!

ICS Client5

I'm assuming you have ICS installed in your Delphi. For some work I wanted to do, I was told that I needed at least Delphi 5. Happily, I was able to lay my hands on a Delphi 7. I can't recall which ICS version I am on... probably an oldish one, to "fit" the Delphi 7. Thank you Overbyte.be for keeping the old versions available!

I hate to lose the "original" versions of anything I've downloaded, so I try to remember to always work with copies.

For today's purposes, copies of all the OverByteIcsCliDemo and the OverByteIcsCliDemo1 files are needed. On my system there were 6 in all, in the ICS/Delphi/Internet folder. Yes, those files... even though I said it was the "Client5" demo which was the key to my success. (I found the naming of the elements of the ICS basic client demo confusing.)

Early on in the process, I renamed the main form "TCP001f1", as I was calling the project TCP001.

When I ran the demo as supplied, I got a window into which I had to fill port and server. And a little puzzle. To ensure that I was giving you the right information, I started again, "from scratch", or so I thought. So how did the "new" instance of CliDemo know the IP address and port I wanted to use? A little digging in the code turned up some "clever" stuff with an ini file. "Clever" stuff you may or may not welcome. I didn't look hard, but I couldn't find the ini file in any place that was obvious to me. Anyway... Once CliDemo is running (the window title says "Client"), you need to tell it about...

You then type something... "xxx" will do... into the Data edit box, hit ctrl-r on your SmartSniff session, and click the "send" button of the Client demo.

All that's needed with those settings, is that device at 192.168.0.241 accepts a connection on port 5200. The device may or may not be able to do all sorts of clever things... but even the most primitive device will accept a connection, and if it does, you'll be able to see in SmartSniff that your PC send "xxx" (or whatever) to the device on 192.168.0.241 (or whatever) port 5200 (or whatever). If the device is faulty or dead, or if you have one of the numbers wrong, or if your anti-malware software is interfering, then after a short delay, in the Client window, you will see "Can't connect".

Remember also that SmartSniff does not update the record of data passed until you de-select the current connection and then re-select it.

Now, if sending "xxx" to your device had any profound effect, I will be amazed. I suppose it might send back an explicit "I don't know that command" message.

With the TEMPerLAN, it will respond if you send it BB 82. And so we come to another detail to be got right. Is that 5 characters of ASCII (counting the space)? Or just two bytes of data? If two bytes, does there need to be a space sent between them?

For TEMPerLAN, it turns out that it is two bytes of data, with no space between them. Furthermore, the sending program should not send a CR or LF following the BB 82. (It does no "harm", actually... but it creates unnecessary traffic on the LAN, so why do it?)

Once you know this, and once you know how, it is easy to get Client to send the Right Stuff....

If you do that and click send, if you have a TEMPerLAN at 192.168.0.241, then SmartSniff should not only show the BB 82 (and the hyphen) going to the TEMPerLAN, it should also see something come back from the TEMPerLAN! The bytes sent by your PC are shown in blue, the bytes received are shown in magenta.

The plus sign on the end is a bit of a temporary kludge along the way to a final answer with no corresponding kludge. The client demo, at least the version I was using, has (I think) a fencepost error... If you ask Client to send "$BB$82" it (correctly) converts the three ASCII characters "$"+"B"+"B" to a single byte, hex BB. Because of what is, I think, a flaw, the program does not, as it should(?) convert the final "$82". However... by adding the plus sign, we can "fix this". The plus sign means that the "$82" is NOT the final part of the string specifying what data to send, and "convert $xx to a byte" mechanism works, and $82 does get converted to a single byte and sent as such. Then the byte that stands for a plus sign in the ASCII code gets sent. Fortunately, the TEMPerLAN just ignores this extra byte.

(The version of CliDemo I was working from had the following in it...

  CliDemoVersion     = 107;
  CopyRight : String = 'CliDemo (c) 1997-2010 F. Piette V1.07 ';

Having seen "BB 82", the TEMPerLAN sends back to whoever sent the "BB 82" (our PC, in this case) the string of bytes which contains number of sensors being read, and the temperatures seen by each. Hurrah!!!

(If you are not seeing this, are you remembering that SmartSniff doesn't always display the latest data? That you have to move away from, and then back to, the connection that interests you, if you want to see the latest data?)

A "Client" optimized for TEMPerLAN

We don't need, for our purposes, to be able to change what we send to TEMPerLAN. But we do need to "capture" what is sent back. So....

We're going to modify SendData procedure. Happily, the original author made it nice and concise. We're going to make it even more concise, because we don't have to "process" the "stuff" coming to SendData from the "Data" (to send) edit box.

After our tweaks, SendData is just...

procedure TTCP01f1.SendData;
var
    Buf : String;
begin
    try
        Buf := #$BB#$82;
        CliSocket.SendStr(Buf);
    except
        on E:Exception do Display(E.ClassName + ': ' + E.Message);
    end;
    ActiveControl := SendEdit;
    SendEdit.SelectAll;
end;

Once that is working... use SmartSniff to see that it is working... you can delete lots of components from the Client form. Delete...

When you try to run the program after deleting those components, you'll have to make a few changes. You don't have to hunt these out... just try to run the program, and fix things as the compiler reports them...

ActiveControl := SendEdit;

... becomes, in two places,...

ActiveControl := SendButton;
SendEdit.SelectAll;

... and...

IniFile.WriteString('Data', 'Command', SendEdit.Text);

... and...

SendEdit.Text   := IniFile.ReadString('Data',
  'Command', 'LASTNAME CAESAR');

... are just removed.

But where's the answer??

At the moment, we only see the answer from the TEMPerLAN if we use SmartSniff. Actually, we can often see a hint of an answer by clicking "Disconnect" after clicking "Send"... but we do not get a satisfactory result even then at this stage.

The result is unsatisfactory because the TEMPerLAN is responding with bytes from the whole range of 0-255, and the demo was set up expecting to handle just printable ASCII. In the code for ProcessCommand, you will see Display(Cmd);

Change that to DisplayHex(Cmd); and create DisplayHex, as follows. Remember to add the forward declaration of DisplayHex up at the top of the code, too. (Just below the forward declaration of Display is the easy and logical place to do that.) Our "DisplayHex" won't be as elegant as the original "Display", because it won't.... until you modify the code presented here for yourself... limit the number of lines in DisplayMemo. That's easy enough to do, erasing the oldest line when the, say, 10th (and subsequent) new lines are added to the memo. Nor does it deal with various clever things that were too high for me.... but it DOES present what was returned by the TEMPerLAN in a readable form... but, so far, only if you click "Disconnect" after clicking "Send". We'll fix that problem next.

Keep the old "Display" procedure, as it is used for putting various messages into DisplayMemo.

Now, at least if click "Send" and then "Disconnect", we can usually see at least some of the data which is coming back from the TEMPerLAN. But we should not need to connect and disconnect repeatedly. And we don't. To understand what is going on, we'll take a little detour to look at how the Client demo works.

Client / TWSocket

The Client demo is built using just one ICS component, the TWSocket component.

There's information about type TWSocket components in the OverByte ICS Wiki.

The following is a story of TCP communications between devices... a widely used answer. There is at least one alternative: UDP. TCP is the foundation upon which many things you will be aware of is built... many email systems, the World Wide Web, for starters. UDP can be a story for another day. (If you want a beginner's discussion of TCP vs UDP, you are advised to read the good TCP / UDP Primer at Overbyte.be, the home of the Internet Component Suite.)

The "conversation" begins with the opening of a channel, a "connection". The process is similar to the establishment of a file handle, if you are familiar with reading and writing datafiles. "Creating a socket" may be exactly the same thing... or it is at least close in my "one eyed king" mind. This requires resources, and so, eventually, the channel should be closed, to return those resources to the general pool that Windows draws upon. One channel creates one line in the upper panel of the SmartSniff display.

In the Client demo (or the derivative we have been creating), have a look at the SendButtonClick procedure. If you haven't clicked SendButton since the application was last restarted (or if for some reason your connection has been lost), there is code in SendButton to start making the connection. We'll come back to that in a moment. If we are already connected to the device we want to talk to via a TCP channel, then SendButton just calls the SendData procedure... the procedure that takes care of sending something off, which we will also come back to. Later.

The CliSocket object in the Client demo is an instance of the TWSocket component, from the ICS. There's a good reference to the TWSocket component online, but I will cover here what you need to know for our TEMPerLAN client.

One property of any TWSocket component is it's State property. And one of the constants built into ICS is wsConnected. In our application, we only have to look to see if CliSocket.State=wsConnected to know if there is an open channel between our computer and the remote device. So simple when you're taken to this point by hand, so hard when you are new, and have too many things to think about at once!

So... if we are connected, we just send our data. But how did we get connected? That is a more subtle story than you might have thought!

Still in SendButton, in the "else", we see several properties of CliSocket being set, and then a call of CliSocket's "Connect" method... but no call of SendData! Doesn't the data get sent the first time we click the SendData button? It does get sent... just not yet. Here's the thing....

When we call CliSocket.Connect, the system goes off and tries to make the connection we've asked for. It may not succeed! Even if it does, it will take time. Hold those thoughts.

Now recall that Windows and Linux are multitasking, event driven environments.

While our little application is running, other things are going on as well. Even within our application, there may be things (like a Timer component counting down) going on that are not "obvious". But those other things can "raise events"... i.e. they can, if we've written our application to respond to those events, "hi-jack" our application, and cause it to run an event handler.

In the Client Demo, there is a handler for an OnSessionConnected event from CliSocket. (The Object Inspector, "Events" tab, is one way to see that this is so.)

When we called the Connect method of CliSocket, "behind the scenes", the CliSocket object started trying to connect according to the settings we'd made in the object's properties. When the attempt finished, successful or not, the OnSessionConnected event arose, and our program went off to the CliSocketSessionConnected procedure, and did what we have there... which was either to display a message saying, "Sorry, failed to connect", or to... shazam!... call "SendData". You should have been impressed when you found that "SendData". In case you weren't, let me recap...

When you click the Send Data button, IF you are "connected" a procedure called SendData is called. ("SendData" is not the handler for the Send Data button.)

If you are not connected, you start the connecting process, but do not... within the handler for the Send Data button... call the SendData procedure. But you do start something which will eventually cause an event (OnSessionConnected) and, within that, if appropriate, the SendData procedure will (at last... but earlier was too soon) be executed.

The SessionConnected event might more descriptively be called "AttemptToMakeAConnectionHasEitherSucceededOrFailed"... but I think we can forgive the authors of ICS for giving it an optimistic name in the pursuit of conciseness?

After the connection

Right! Moving on! If we made a reasonable request, we are by now connected. Our computer and some remote device (the TEMPerLAN, in this case study) are connected. You can imagine it as if telephone wires have been strung between them, although of course the physical wires (or WiFi link) were present all the time.

Once the connection is there, it is time for the innards of the SendData procedure....

The heart of that is...

CliSocket.SendStr(Buf);

We prepared the way for that by putting what we wanted sent into Buf before calling SendStr. As you might guess from the names, "Buf" is a buffer, holding a string of bytes, and SendStr tries to send it off to whatever the socket is connected to. (And then again, you might have guessed any one of a number of other things, which is why I wrote out what I did!)

Note that the bytes in a string don't have to be bytes from the range assigned to printable ASCII. If you say Buf:=#$41#$42#$43 you are working harder than you need to. You could just have said Buf:='ABC'. But the TEMPerLAN, bless it, wanted some non-printable bytes sent to it to make it "do its trick" (send back the temperature readings). Hence the Buf := #$BB#$82;

And that's pretty well it, for SendData... apart from dealing with errors which may be reported via an event triggered by the call of SendStr.

So how do we get our answer???

Receiving data via TCP

There are multiple ways to skin a cat, and multiple ways to communicate over LANs and WANs.... this tutorial covers one way, the LineMode way. That, for TEMPerLAN, is the wrong way, I realized, a long way into the project... sigh. There's a discussion of why it is the wrong way later. For now, just pretend (it isn't true) that an $FF will never occur within what the TEMPerLAN returns to the client, except as the End Of Line (End of Record) indicator. Even though we are using LineMode with a TEMPerLAN, our application will USUALLY work fine. It won't work if the sensors are cold (below freezing). If the sensor is gradually warming or cooling, and, just by chance, sending every possible reading along its range, you will also get an error once in every 256 readings. So if you get errors from time to time, just grasp the sensor with a warm hand, change its temperature. You have a 255/256 chance of changing it to something that will be read properly, even by our flawed program.

I've re-written the code, using a different approach, one that doesn't care if a "stray" $FF arises. When I get time, I will include the compiled .exe in this tutorial's .zip file. It will be called TCP002.

Let's get back to work!

At the point a while ago, in the code for what settings would be sent to CliSocket, our instance of the TWSocket object, we said we wanted LineMode=true for the connection, with the LineEnd character set as 255, aka $FF.

(The line end character can, by the way, be any byte or short string of bytes. A two byte string consisting of the codes for a line feed and a carriage return is often used as the LineEnd "character". They are represented many ways, e.g. "CR/LF" (in essays), or #13#10, or #$0D#$0A.)

With the socket set up that way, here's what happens when our connection is up and running:

A buffer in the bowels of ICS and the operating system accumulates any bytes arriving over the connection until the LineEnd byte (or string of bytes) is seen.

At that point, an OnDataAvailable event arises, the demo has attached the handler called CliSocketDataAvailable to that. Note that, in these terms, data isn't "available" until a whole line of data has become available. "A whole line" is defined by the arrival of the LineEnd byte or byte-string.

Once a line of data is "available", as you can see in the CliSocketDataAvailable procedure, you can fetch that line from the bowels of ICS with the Retrieve method. There 's also a RetrieveStr method, which I though looked less obscure... no need to use "@", no need to append the Null (#0)... but the people who wrote the Client demo know more about these things than I do, so I left things as they had them.

So much for most of what you need to know about working with TPC, "the connection", sockets, etc. There is a little housekeeping... when you are done, you should close the connection (see DisconnectButtonClick). The author of the demo seems content to let the general provisions of application shut-down take care of any necessary closing of the connection. In other words, if you choose to close the application without first disconnecting the TCP channel, then there will be no problems for your computer and the other things running in it.

You should have an event handler to tell you if the connection is broken... it may be closed from the other end at some point, for instance. In the demo, this is handled by CliSocketSessionClosed, which is the handler for CliSocket's OnSessionClosed event.

I have ducked explaining at least one area which may be important. Here's one that I know about.

There are some properties of the socket with "KeepAlive" in their names. I believe this is something to do with periodic checks that the channel is still there, but I don't know. For now, and for the purposes of reading temperatures from the TEMPerLAN, the default values for these properties seem sufficient.

Enough about keeping alive.

You will also find material in the demo, e.g. in FormClose about an ini file. The ini file is only there so that you don't have to keep re-entering the port and server you want to connect to. Oh, and it also provides the way the application remembers any re-sizing of the window you may have done. Excellent things, ini files, but not something you need to use the ICS. (An ini file function is provided in ICS... GetIcsIniFileName... which returns a string. That string provides a path and name for a file, and the demo uses that file for the application's ini file. If you changed...

IniFileName:= GetIcsIniFileName;

... to...

IniFileName:='C:\TCP001ini.txt';

... then you would make the ini file for this application be C:\TCP001ini.txt, which is a good name, but a very crude "answer" to determining where the ini file will be stored. I think... no promises, but there is a way... that the following would put the ini file in the .exe's folder...

IniFileName:='TCP001ini.txt';

Nearly there

Well... sort of. The hard TCP stuff is out of the way, anyway. At least I hope I've explained all of that. Re-read what is above, if you are still perplexed on a TCP issue.

What's left? What haven't we dealt with??

Remember that, for argument's sake, you have a TEMPerLAN, and you want a program to monitor what its sensors are seeing.

By the way... you can put 192.168.0.241 into the "server" edit box to look at a TEMPerLAN at that IP address on your LAN... or you can put mon7NC.dyndns.org into the "server" edit box (still 5200 in the "port" edit box) if you have connected the TEMPerLAN to the internet via the URL given. No changes are needed to TCP001... but the setting up of the other elements, while not rocket science, isn't trivial. See my notes on FarWatch or the ArduServer if you are interested.

By the way... one little flaw in the user friendliness of the software... If, after connecting to something, you change what is in either of the edit boxes and click "Send" again... No notice is taken of what you put in the edit boxes. The BB 82 will still be sent where it was sent previously.

You might think that simply adding a

DisconnectButtonClick(nil);

... to a ServerEditChange routine would be the answer, but that gets a bit tangled... you can't for instance, call DisconnectButtonClick while the system is in the midst of an attempt to connect.

The fix probably ought to be done, but I haven't the energy for it now! (Maybe just disable the two edit boxes while a connection exists, or is being attempted? This would force users to click "Disconnect" (you would add "re-enable edit boxes" to that) before the user could try to change the port or server?

ANYWAY... what was the bit that hasn't been dealt with yet??



...



...



... last chance to think of it for yourself!...



v v v



Did you say "Display the data as temperatures, instead of as computer friendly strings of hex."?

If so: Gold star!

At the moment, when the data comes back, thanks to our "DisplayHex" procedure, it looks something like...

BB 82 03 01 EE 01 80 01 71

The string starts with BB and 82. The next number is the number of temperature sensors connected to the TEMPerLAN. Then, 2 bytes per reading, we have the readings. Remember that what was actually sent was the above, plus a FF at the end, to mark the end. The FF is stripped off before the string goes to DisplayHex.

Before we can proceed, a nasty little fact has to be faced.

Suppose the reading from the first sensor is, say 01 FF?

That datum would fool our TEMPerLAN reading software. The DataAvailable routines are going to think that the FF in the temperature reading marks the end of the whole message. Not only will this leave us with a too-short message now, but in a moment, we well get the second half... we'll be sent...

01 80 01 71

... as if THAT were a proper record from the TEMPerLAN, and new problems would arise!

Happily, there is a solution... but I'm not going to explain it in detail here. We will proceed with our discussion of how to do client/ server stuff using LineMode=true on the (flawed) understanding that the TEMPerLAN won't send $FF, except as the end of line marker. (It will send $FFs once in a while, but at above freezing temperatures, rarely.)

I'm going to ramble for a little bit. You can skip down to the next heading, if you want to.

The whole "what can we do about the $FFs" question took me down several paths which illustrate nicely the messes you can get into if your feet (or, in the case of programming, I suppose we should say fingers) are moving faster that your brain.

When you try to fudge things, it usually ends in tears. Sometimes we attack the wrong problem. In this program, in spite of all I had invested in the LineMode=true "answer", using LineMode was the problem. Changing that was what I needed to do. But look at the ways I tried to squirm out of doing things the right way....

First, I said to myself, maybe the Nice People at PCSensor built the TEMPerLAN with a little fudge in it. It would be easy enough to fix things so that the TEMPerLAN would report, for instance, 01FE when the reading was actually 01FF. Would a sixteenth of a degree Celsius ever matter?

Always be alert for any such "would it matter" thoughts. They almost always arise when you are thinking of doing something foolish.

If getting 01FF into the data packet were our only problem, then the little cheat might have saved the day. But I don't think that the PCSensor engineers did that. For one thing, I did, about 1 time in 256, get an error which could have been caused by a datum containing an $FF. But there's a "deal breaker"....

Changing 01FF to 01FE is do-able, and not so very terrible.

However... to send temperatures which are slightly below zero, the TEMPerLan needs...

Unless you're willing to have the first 256/16ths of a degree below zero all report as -257/16ths of a degree below zero, the "send any FFs as FE" answer doesn't work. Sigh. But the fact that we were trying to fudge things with a little "well this wouldn't matter much" answer should have been a red flag.

So. That won't work. Still trying to save the wrong thing, still trying to save doing it with a LineMode=true answer, I then turned to the following dog's dinner...

I could, I told myself, write things to take the "data FF" into consideration... if I got an FF "too soon" (and of course, just determining that would be one headache) I could "save" what had arrived so far, and fetch another "line". Then I'd add the two together, and use that. Yes, well, maybe you could. (You'd have to provide for the fact that there might be more than one "data FF" before the EndOfLine FF, by the way.) But going back to the initial faulty premise: "Use LineMode=true", and changing that proved to be the right answer.

But it would mean a very extensive re-write of this tutorial, and a re-write of the code. And there are many cases where using LineMode=true is fine. So I left the tutorial alone!

You can use the nice, easy, LineMode=true approach whenever you can identify a byte or byte-string which will not arise within the line you are trying to send. If TEMPerLAN were re-programmed to send the temperatures in ASCII, a simple thing to do, a TEMPerLAN client program could use LineMode=true.

TCP002.exe (to be included in the TCP001.zip soon, if it isn't there already) was written with LineMode=false. It will read and report every temperature that TEMPerLAN can send.

End of Ramble.

Temperatures, not computer gibberish

I could just give you "the answer", but this is meant to be a tutorial, so I will take you through how I got to the answer.

At this point, our program has just one place where it calls "DisplayHex".

We're going to leave the DisplayHex procedure in place, just in case we need to go back to looking at the raw data from a TEMPerLAN (or similar device) at some point.

But replace the call "DisplayHex(Cmd)" with "DisplayTtures(Cmd)", and set up the following DisplayTtures shell...

procedure TTCP01f1.DisplayTtures(Msg : String);
var
    sTmp: string;
begin
  sTmp:='There were '+inttostr(length(Msg))+' bytes in the record';
  DisplayMemo.Lines.Add(sTmp);
end;

What follows will not be the most elegant way to achieve our end... but I hope it will be exceedingly transparent and robust.

At the start of DisplayTtures, we are going to look at the string that was passed to it, and check it for various possible flaws. While we're at it, we're going to extract a piece of information, if the string is valid. A CheckRecord procedure will be used; it will pass things back to the place where it is called by using var parameters. To get that started, we will look to see if the first byte of the string is hex BB. Here's all of DisplayTtures again, in its growing form. Try that, and then just change the BB to BA, so you can see what happens if the record doesn't meet expectations.

procedure TTCP01f1.DisplayTtures(Msg : String);
var bErr,bCountTtures:byte;
   {subroutines of DisplayTTurees...}
   procedure CheckRecord(Msg:string; var bErrL,bCountTturesL:byte);
   //The variables do not need the "L" suffix for things to work, but
   // having them may save confusion for readers not yet fluent in matters
   // of scope.
   begin
     bErrL:=0;// if this is still zero at the end of the procedure
       //call, then no errors were found.
     bCountTturesL:=255;//a rogue value, but to give this variable
       //SOME, defined, value, in case it doesn't get one otherwise
     if length(Msg)=0 then bErrL:=1;
     if (bErrL=0=0) and (Msg[1]<>#$BB) then bErrL:=2;
   end;//of CheckRecord
   {end of subroutines of DisplayTTurees...}
   var
    sTmp: string;
begin
  CheckRecord(Msg,bErr,bCountTtures);
  sTmp:='There were '+inttostr(length(Msg))+' bytes in the record. ';
  if bErr=0 then sTmp:=sTmp+'No errors seen in record.'// no ; here
            else sTmp:=sTmp+'One or more errors seen in record.';
  DisplayMemo.Lines.Add(sTmp);

end;

That pretty well illustrates most of what we need to know. We will do more of the same. Eventually, if it turns out that the string we are dealing has survived all out data validation tests, the data will be converted to numbers that a non-geek can comprehend. But these things are mere details. I will not, in this essay, go beyond a simple application to periodically fetch data from the TEMPerLAN and display it as text. Putting the numbers in a graph would be a significant extension, as far as the application's user friendliness is concerned... but not an interesting programming challenge different in any way from plotting values from a local sensor. (So seek out a different tutorial if it is plotting data you want to know about!)

So far we've checked that the string we received has at least five bytes in it, in addition to the $FF which was interpreted as signaling the end of a line, aka a record. There must be at least this many bytes in the record, even if the TEMPerLAN is connected to only one sensor.

(The terminal $FF was available to our program, but code from the demo, in ProcessCommand, removed it, and I didn't notice that was where the $FF was "disappearing" until after this essay was "done".)

We have checked that the first byte in the string is a $BB, which is what TEMPerLAN is supposed always to start the record with.

Next we will....

Check that the second byte is an $82, also constant in reply to the command we're providing a client for.

If things haven't fallen over yet, we will pick up the third byte, and store that as the number of sensors connected to the TEMPerLAN, which is what the third byte is supposed to tell us.

We'll need that number for the next test, and if the next test fails, it will tell us that either the TEMPerLAN isn't sending what it should for readings, or that the "number of sensors" datum was incorrect.

If we're still in business, which we should be, "all" that 's left now is to harvest the readings data, convert them to human friendly form, and display the readings!

Our reward for all that careful planning is that implementing it is quite easy.

Two big bits of that were dealt without anything fancier than....

if (bErrL=0) and (Msg[1]<>#$BB) then bErrL:=2;
     if (bErrL=0) and (Msg[2]<>#$82) then bErrL:=3;

Fetching the number of sensors reported, and whether the overall record is the right length for that wasn't a lot harder. All of these things were done in the CheckRecord subroutine. If it returns 0 in bErrL, then all was well. Any other number indicates an error, and which error can be determined by looking at the code. If bErrL is zero, then the number in bCountL is how many temperature readings were returned by the TEMPerLAN. (If bErrL is not zero, the number in bCountL may well be meaningless.)

In a moment, I'll give you the code which does what I've described so far. Note that we have still funked actually collecting, converting and presenting the temperature readings. Note a little "extra" I've slipped in: The data lines now have a "B:" (for "bad") pre-pended to output strings reporting some problem, and "G:" pre-pended to output strings with no problem. The output strings will eventually hold the readings. We may also want to provide the user with a hex dump of the raw record from the TEMPerLAN. Any such debugging information will be given a "D:" prefix.

procedure TTCP01f1.DisplayTtures(Msg : String);
var bErr,bCountTtures:byte;
    sTmp: string;

   {subroutines of DisplayTTurees...}
   procedure CheckRecord(Msg:string; var bErrL,bCountTturesL:byte);
   //The variables do not need the "L" suffix for things to work, but
   // having them may save confusion for readers not yet fluent in matters
   // of scope.
   begin
     bErrL:=0;// if this is still zero at the end of the procedure
       //call, then no errors were found.
     bCountTturesL:=255;//a rogue value, but to give this variable
       //SOME, defined, value, in case it doesn't get one otherwise
     if length(Msg)<5 then bErrL:=1;
     if (bErrL=0) and (Msg[1]<>#$BB) then bErrL:=2;//Check correct record 1st byte
     if (bErrL=0) and (Msg[2]<>#$82) then bErrL:=3;//Check correct record 2nd byte
     if (bErrL=0) then begin // start of block executed to
           //fetch number of readings, see if total record size right for that
           //number of readings....
           bCountTturesL:=ord(Msg[3]);
           if length(Msg)<>3+2*bCountTturesL then bErrL:=4;
          end;//Block executed to fetch number of readings, check record length
     //if bErrL still 0, then problems not found. USING the readings in the
     //  record used OUTSIDE of this subroutine.
   end;//of CheckRecord

   function CreateAnswerString(MsgL:string; bReadings:byte):string;
   begin
   result:='G:Record had '+inttostr(bReadings)+' readings.';
   end;// of CreateAnswerString
   {end of subroutines of DisplayTTurees...}

begin //main block of DisplayTtures
  CheckRecord(Msg,bErr,bCountTtures);//if bErr=0 after this, all was well.
  sTmp:='There were '+inttostr(length(Msg))+' bytes in the record. ';
  if bErr=0 then sTmp:=CreateAnswerString(Msg,bCountTtures)// no ; here
            else sTmp:='B:One or more errors seen in record. '+
               'One was error ID '+inttostr(bErr);
  DisplayMemo.Lines.Add(sTmp);

end; // of subroutine DisplayTtures

Now we will create the code needed in "CreateAnswerStringForGoodRecord". It may look complex, but remember... it is just changing a number of 16 bit (2's complement) numbers expressing temperatures in sixteenths of degrees Celsius into ASCII strings, of a fixed format, expressing the temperatures in decimal numbers of Celsius degrees.

I really don't think you need to study this particularly closely. The important parts of this tutorial are elsewhere!

To avoid certain issues, only integer data types were used in the programming. I would like to thank whoever created the DS18B20 reading routines found in many Arduino tutorials, the ones not using an Dallas library, for showing me the "divide by 6.25" trick... I first saw it in some code from NuElectronics.com, where I bought a nice datalogging shield.

The temperature is first converted into hundredths of degree Celsius, and then the lower two digits of that are put in one variable (smiTtureD ("decimal" part)), and the rest of the number, the number of whole degrees Celsius is put in another variable (smiTtureU ("upper" part)), and then with those the strings are built.

Please don't abbreviate "temperature" as "temp", which is too easy to interpret as "temporary". Use "tture"?

Anyway... be that as it may, here's the code!

   function CreateAnswerStringForGoodRecord(MsgL:string; bReadings:byte):string;
   var sTmp, sTmp2, sTmp3:string;
       smiTtureU,smiTtureD:smallint;//"U" units part, "D" decimal part,
          //but smiTtureU used in early stages to hold whole tture
          //while it is still expressed in 1/16ths of a Celsius degree,
          //and smiTtureD used as a "Tmp" briefly...
       bLoop:byte;
   //If TEMPerLAN had 2 sensors, reading -2.3 and 15.2, then the string
   //created should be...
   //G:02 -002.30 +015.20
   //The answer should always consist of...
   //'G:', then
   //Number of temperatures, with leading 0 if <10, then
   //The readings, each of them starting + or -, and each using
   //    three digits before the decimal point, two after it.
   //    (zero to be reported as +000.00)
   //This rigid, fixed length format, is to help programs as
   //    yet unwritten which will use the data brought this
   //    far, so far.

   //This subroutine looks fearsome, but it is just a bunch
   //    of stuff to interpret the temperatures as reported
   //    by the Dallas 18B20 chips, and to express them in
   //    the format I decided upon.

   //The readings arrive as 16 bit, 2's complement numbers.
   //    $0001,$0002,$0003.. mean 1,2,3!
   //    $FFFF is -1.... i.e. that the temperature is 1/16th
   //    of a degree Celsius below zero.

   begin
   sTmp:=inttostr(bReadings);
   if bReadings<10 then sTmp:='0'+sTmp;
   sTmp:='G:'+sTmp;

   //Use the following to insert test data, to see if various
   //   data give the correct required result.
   //One pair per reading, MSB first. Don't use more
   //   elements of MsgL that is appropriate for the number
   //   of sensors on your LAN. E.g. if you only have two
   //   sensors, rem out the MsgL[8] and MsgL[9] lines.
 {  MsgL[4]:=#$0;
   MsgL[5]:=#$1;
   MsgL[6]:=#$0;
   MsgL[7]:=#$0;
   MsgL[8]:=#$FF;
   MsgL[9]:=#$FF;
  }
   //Delphi and inttostr convert smallint type (signed 16 bit)
   //  data quite nicely... but without the leading and trailing
   //  spaced I wanted.

   //More use of real numbers was another possibility, but one
   //  I wanted to avoid.

   for bLoop:=1 to bReadings do begin
       sTmp2:=' ';//start building a number, pre-pended by a space
       smiTtureU:=256*ord(MsgL[2+bLoop*2])+ord(MsgL[3+bLoop*2]);
       if smiTtureU<0 then begin
          sTmp2:=' -';//start building a number, pre-pended by a space
          smiTtureU:=smiTtureU*-1;
          end //no ; here
         else begin
          sTmp2:=' +';//start building a number, pre-pended by a space
          end;//else
       //Multiply number of 16ths by 6.25 to make it number of 100ths
       smiTtureD:=smiTtureU div 4;
       smiTtureU:=smiTtureU*6+smiTtureD;
       //smiTtureU now holds tture in 100ths of deg C
       //now work out decimal part of the tture...
       smiTtureD:=smiTtureU mod 100;
       //convert smiTtureU (100ths) to degrees, shorn of fraction part
       smiTtureU:=smiTtureU div 100;
       //convert each to a string, with leading zeros as needed...
       //  adding bits to the final answer buffer as we go...
       sTmp3:=inttostr(smiTtureU);
       while length(sTmp3)<3 do sTmp3:='0'+sTmp3;
       sTmp2:=sTmp2+sTmp3+'.';

       sTmp3:=inttostr(smiTtureD);
       while length(sTmp3)<2 do sTmp3:='0'+sTmp3;
       sTmp2:=sTmp2+sTmp3;
       sTmp:=sTmp+sTmp2;
       end;//... end of loop to add bReadings ttures to output string.

   result:=sTmp;
   end;// of CreateAnswerString

Reading again and again... automatically

It gets rather boring hitting the "Send" button over and over again. Because of the way we put the program together, it is easy to get it to read the TEMPerLAN repeatedly. All we need to do is add a Timer component. I called it tiReadAgain, to reflect the point of having it.

In the FormCreate procedure, I set the timer's enable property to false, and the interval to a default 2000, which makes the read interval 2 seconds. When it times out, the SendButtonClick procedure is called.

I created a button which enables the timer. It also has its enabled property set to false in FormCreate. The button is enabled when the connection to the TEMPerLAN has been made, and disabled again (and the timer disabled) when the connection is broken.

Button buReadAgain (labeled Enable Auto-read), enables the timer. Duh?

I won't promise that every detail of that is right at this time. But it gives you an idea of the things that need to be dealt with, if you want repeated reading to occur automatically.

Of course, once you have an auto-read in place, what should happen in the memo becomes an issue. Should new lines just be added, added, added? Once there are, say, 50 lines of data, should the oldest be removed when a new one is added? Newest at top? At bottom? Auto scroll the memo, in the case of the latter? And so on.

A way for the user to change the frequency of auto-read should be provided. That setting should be recorded and restored via the ini file.

So many bells and whistles! So little time!

What was the point??

What was the point of all that? The application we created? I don't think so! Except as a starting point for a more polished application.

As an exercise on which you can sharpen your teeth? Absolutely. If working through that has expanded your skills, then I am delighted.

Proof that the PCSensor TEMPerLAN is a good device, which actually works, but could use better documentation, and some alternative software? That too!

My bad design choice

As explained above, forgive me if I re-cap...

A long way back, I elected to read the data coming from the TEMPerLAN after it is sent a $BB $82 using the "line at a time" method.

This was a Bad Idea, for a client application for the TEMPerLAN, which sometimes sends $FF as part of the line. A different approach should have been taken, for the TEMPerLAN client. (LineMode is fine for many needs.)

In the LineMode=false mode, there's a buffer to hold things arriving via the TCP/IP connection... the problem is that while the whole line from the TEMPerLAN often arrives in one "chunk", there's no guarantee.

Broadly speaking, what we need to do is...

In practice, in TCP002, I set up a "buffer for the buffer". Whenever something came in, I appended it to the contents of a global string-type variable. As that grew, I watched it, and then dealt with the contents when the whole response to the $BB $82 command had arrived.

For any "Olivers" out there... The place to learn more is...

The place to go to learn more is the ICS Wiki. To do things by the "other" method, the one that happens when the socket's LineMode property is set to False, you still use the OnDataAvailable event to know that data has arrived, but that event will be triggered as soon as some data is available, it won't always wait for the transmission of the whole line. You still use the component's Receive, or ReceiveStr method within your OnDataAvailable handler.

It isn't terribly difficult to make what you've created "visible" to anyone on the internet. You will need either a static IP address (which, if you had one, you would know you have), or a dyndns service. I've done a separate guide to why you need, how to set up dyndns service.





            powered by FreeFind
  Site search Web search
Site Map    What's New    Search This search merely looks for the words you enter. It won't answer "Where can I download InpOut32?"


Click here if you're feeling kind! (Promotes my site via "Top100Borland")

If you visit 1&1's site from here, it helps me. They host my website, and I wouldn't put this link up for them if I wasn't happy with their service. They offer things for the beginner and the corporation.www.1and1.com icon

Ad from page's editor: Yes.. I do enjoy compiling these things for you. I hope they are helpful. However... this doesn't pay my bills!!! Sheepdog Software (tm) is supposed to help do that, so if you found this stuff useful, (and you run a Windows or MS-DOS PC) please visit my freeware and shareware page, download something, and circulate it for me? Links on your page to this page would also be appreciated!
Click here to visit editor's freeware, shareware page.

Link to Tutorials main page
How to contact the editor of this page, Tom Boyd


Valid HTML 4.01 Transitional Page tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org


If this page causes a script to run, why? Because of things like Google panels, and the code for the search button. Why do I mention scripts? Be sure you know all you need to about spyware.

....... P a g e . . . E n d s .....