System designHow-to

Send commands to a smart camera over TCP in C#

Wire a C# TcpClient to a smart camera TCP command port: send ASCII commands with the right terminator, read chunked replies in a loop, set timeouts that fit.

LumaSpec Vision11 min read Share on LinkedIn

On this page 10 sections
  1. 01What you need
  2. 02Open the connection with a connect guard
  3. 03Send the command as ASCII with its terminator
  4. 04Read the reply in a loop, never with one Read
  5. 05Worked example: a 28-byte reply arriving as 12 then 16 bytes
  6. 06Set timeouts from the camera's cycle time
  7. 07Common mistakes and the test for each
  8. 08Where the C# program runs
  9. 09Pre-flight checklist
  10. 10Questions

A smart camera that accepts commands over TCP behaves like any other network service: it listens on a port, reads an ASCII line, and writes an ASCII reply back. That is enough to trigger inspections, switch recipes and query results from your own C# program — the whole protocol is a page of code plus the manual's command list. Two details decide whether the link is reliable rather than mostly working: the line terminator you send, and the way you read the reply, because TCP delivers bytes in chunks of its own choosing. This guide walks the connect–send–read sequence with the framing rules, the timeouts and the failure paths that keep a camera link deterministic on the factory network.

What you need

Four things, all quick to gather before the first build:

  • A smart camera whose manual documents a TCP command channel. The manual names the three values no one should guess: the command port, the line terminator and the command set. Those lines are the entire protocol.
  • The camera's IP address, with your PC's network card on the same subnet. Industrial cameras commonly default into the 192.168.x.x range; the code below uses 192.168.188.2 as a placeholder, not a recommendation.
  • A PC or controller to run the program. The code needs only System.Net.Sockets and System.Text, so plain .NET is enough; nothing is installed on the camera itself.
  • One harmless query command from the manual — something that reads status, so the first test cannot move a machine.

Command traffic is tiny — a few lines of text per cycle — so any Ethernet camera interface carries it with ease. Two examples from the shop:

Smart cameras & vision sensorsHikrobot MV-SC3050XC-16M-ATN-H 5 MP color vision sensorMV-SC3050XC-16M-ATN-H
Interface
Fast Ethernet
Resolution
5 MP
$1,809.00View part
Smart cameras & vision sensorsHikrobot MV-SC5060XM-08M-WBN SC5000X series Smart Camera, 6 MP, Mono, 8 mm focal length, M12-mountMV-SC5060XM-08M-WBN
Interface
GigE
Resolution
6-10 MP
$1,629.00View part

The MV-SC3050XC-16M-ATN-H is the 5 MP color vision sensor on Fast Ethernet; the MV-SC5060XM-08M-WBN is the 6 MP mono smart camera with an 8 mm M12-mount lens. Which command set a given model exposes, and on which port, is stated in its user manual. The smart cameras category lists the full range, filterable by interface.

Open the connection with a connect guard

You are the TCP client and the camera is the listener — the same direction logic that matches a barcode reader to its host. One camera also serves several clients at once: your command link can coexist with a second application that reads the camera's parameter tree over TCP.

A bare Connect call to an address with nothing behind it waits on the operating system's SYN retransmissions — about 21 s on a stock Windows stack. Cap it with ConnectAsync and Wait(2000), which gives up after 2000 ms and returns false.

using System;
using System.Net.Sockets;
using System.Text;

// Example values in the style of a camera command link.
// Your camera manual lists the real address, port and command names.
const string cameraIp    = "192.168.188.2";
const int    commandPort = 49211;

using var client = new TcpClient();
if (!client.ConnectAsync(cameraIp, commandPort).Wait(2000))
    throw new TimeoutException($"No TCP connection to {cameraIp}:{commandPort} within 2000 ms");

NetworkStream stream = client.GetStream();
stream.ReadTimeout  = 5000;   // ms - see the timeout section
stream.WriteTimeout = 5000;   // ms

cameraIp and commandPort are example values; TcpClient, ConnectAsync, Wait, GetStream, ReadTimeout and WriteTimeout are documented .NET API names. The listing uses C# 8 using declarations; on an older compiler, wrap the client in a using block instead.

Two failure modes behave differently, and the guard tells you which one you have: a camera that is up but not listening on that port refuses within milliseconds (the stack returns a reset), while an unreachable address is what the 2000 ms guard catches.

Send the command as ASCII with its terminator

Commands are ASCII text. Build the bytes with Encoding.ASCII — Encoding.Unicode ships 2 bytes per character and silences most cameras. Terminate with the line ending your manual names: camera command protocols in the field use \r\n, \n or \r, and \r\n is the default to try first. If a command goes unanswered, suspect the terminator before the command text.

string command = "jobinfo";   // example command name - your manual defines the set
byte[] outBuf  = Encoding.ASCII.GetBytes(command + "\r\n");
stream.Write(outBuf, 0, outBuf.Length);

One line from the classic example deserves retirement: Flush. A NetworkStream keeps no send buffer of its own — Write hands the bytes straight to the TCP stack — so Flush performs no operation on it. Leave it out.

Commands that switch recipes deserve the same naming discipline as serial links: one unambiguous command code per recipe, exactly as in RS232 vision recipe selection. The transport changes; the rule does not.

Read the reply in a loop, never with one Read

TCP is a byte stream, not a message courier. The .NET class library documentation for NetworkStream.Read states the contract: the method returns whatever bytes are available at that moment, between 1 and the count you passed. It knows nothing about your message boundaries. Read blocks until at least one byte arrives or ReadTimeout fires; a return of 0 means the camera closed the connection. Read that contract page in the official .NET API browser for your framework version before you rely on the details.

A reply is complete when your framing says so — at the terminator — not when Read has returned once:

var reply = new StringBuilder();
var inBuf = new byte[1024];
while (true)
{
    int n = stream.Read(inBuf, 0, inBuf.Length);    // 1..1024 bytes now; 0 = camera closed
    if (n == 0) break;
    reply.Append(Encoding.ASCII.GetString(inBuf, 0, n));
    if (reply.ToString().IndexOf('\n') >= 0) break; // terminator seen: message complete
}
string result = reply.ToString().TrimEnd('\r', '\n');

Pass the local buffer's length, 1024, as the count — not the socket's ReceiveBufferSize, whose .NET default of 8192 bytes tunes transport buffering, not message length. If your protocol instead promises fixed-length replies, loop until you hold exactly that documented byte count; for ASCII command sets, the terminator loop above is the norm.

Worked example: a 28-byte reply arriving as 12 then 16 bytes

The inputs, and where each comes from:

  • The reply line RESULT,01,X=100.50,Y=30.20\r\n — an example inspection result in the comma-separated style typical of camera replies.
  • The arrival split, 12 bytes then 16 bytes — a typical arbitrary split; TCP chooses segment boundaries itself, and small replies straddle them routinely on a loaded switch.
  • Reader A, the classic one-shot pattern: a single Read into a new byte[10025].
  • Reader B, the terminator loop from the previous section.

The rule in words: TCP preserves bytes and their order, never message boundaries. A message ends where your framing ends it.

The arithmetic:

  • The payload RESULT,01,X=100.50,Y=30.20 is 26 characters; the \r\n terminator adds 2 bytes: 26 + 2 = 28 bytes total.
  • Reader A's one Read returns the 12 bytes that have arrived — "RESULT,01,X=". A GetString over the whole 10025-byte array then produces 12 characters followed by 10013 NULs (10025 - 12 = 10013). The string contains no '\n', so the parser reports no reply while 16 bytes (28 - 12 = 16) are still due.
  • Reader B: the first Read returns 12 and the accumulated text holds no '\n', so the loop runs again. The second Read returns 16 — "100.50,Y=30.20\r\n" — putting the '\n' at index 27, the 28th byte. 12 + 16 = 28: the reply is complete and trims to RESULT,01,X=100.50,Y=30.20.
message bytes = payload characters + terminator bytes 28 bytes = 26 + 2 unread after one Read = 28 - 12 = 16 bytes NUL padding in a full-buffer GetString = 10025 - 12 = 10013 characters

The test that separates the two readers: run each 20 times against the camera. Reader A truncates only on the cycles where the reply straddles segments — intermittent, and therefore hard to catch on a quiet bench. Reader B reads 28 bytes on every run.

The decision: for line-terminated ASCII, the norm for camera command sets, read until the terminator. A single Read is correct only in a fixed-size protocol, where you loop until you hold exactly the documented byte count. Splitting the completed line into values is the next step; parsing four vision results from one socket string shows that discipline.

Set timeouts from the camera's cycle time

ReadTimeout and WriteTimeout apply per call, in milliseconds, and default to infinite — without them, a half-dead link hangs the thread forever. Working values:

  • Connect guard: 2000 ms, as above.
  • ReadTimeout: 5000 ms to start, which leaves roughly an order of magnitude of headroom over the command-to-reply times described in the FAQ above.
  • After measurement, tighten to about twice your worst command-to-reply time. A worst case of 800 ms earns a 2000 ms timeout (2 x 800 = 1600, rounded up to a standard value).

A timeout surfaces as an IOException around the Read call. Catch it, close the socket, reopen, and count the event: a rising timeout count is the earliest warning of a saturating network or a camera stuck mid-inspection.

Common mistakes and the test for each

  1. Camera silent on a connection that opened fine. Test the terminator: send the same command with \r\n, then \n, then \r — one of the three answers. The wider no-response checklist, including send-loop framing and who talks first, is in robot socket communication: fixing the send loop.
  2. Reply comparison fails on trailing garbage. The string was built from the whole buffer. Log reply.Length and the byte count each Read returned; build with GetString(inBuf, 0, n).
  3. Byte count is twice the character count. The command was encoded with Encoding.Unicode. Fix: Encoding.ASCII, so bytes written = characters + terminator.
  4. Connected to the image port. It answers with protocol noise or closes at once. Command ports and image ports are separate; the manual's port list decides.
  5. Zero-byte read treated as "wait for more". A 0 return means closed, and looping on it spins a core at full load. Treat it as close-and-reopen.
  6. The camera should speak first. If your camera pushes results on its own schedule instead of answering commands, the roles flip and your program becomes the listener — the smart camera OCR string output over TCP case.

Where the C# program runs

On the line, the host is a small PC or a fanless controller next to the cell. A vision controller earns its slot through its Ethernet budget: the cameras sit on a private subnet that never touches office traffic, so command latency stays free of e-mail and browser noise.

Vision controllersHikrobot MV-VC3301H-128G66 I3-8100, 8GB+128G SSD, No PoE, PCIe expansion supported, Incl. VM6200Pro licenseMV-VC3301H-128G66
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$2,419.00View partBuild a system with it

The MV-VC3301H-128G66 pairs an Intel Core i3 with 8 GB of memory, carries 5+ LAN ports for that private camera subnet, and includes the VM6200Pro license shown in its listing. The full range is in the vision controllers category.

When the client is a PLC rather than a C# program, the framing rules are identical — same terminator, same read-to-terminator discipline. The PLC-to-camera TCP setup guide walks that pairing, and when the results must land in PLC registers afterwards, a register handshake takes over from TCP.

Pre-flight checklist

Before the first connect:

  • Camera IP pinged from the host, PC NIC on the same subnet — no link without it.
  • Command port, terminator and one query command copied from the manual, not from memory.

Before the first automated cycle:

  • Connect guard at 2000 ms; ReadTimeout and WriteTimeout at 5000 ms.
  • Command bytes built with Encoding.ASCII plus the terminator, with the byte count logged.
  • Read loop runs to the terminator, or to the documented fixed byte count; a zero-byte read reopens the link.

Before it ships:

  • Worst command-to-reply time measured over at least 100 cycles; ReadTimeout tightened to about twice that.
  • Timeout and closed-connection paths counted and alarmed, not swallowed.

Each line exists because skipping it fails quietly: the terminator and the read loop decide whether the link works, and the timeouts decide whether it fails loudly instead of hanging. If you are still choosing hardware, the camera finder filters by interface and resolution, the configurator walks camera, lens, light and controller in one pass, and the systems topic page collects the integration guides around this one. For a second opinion on a link that misbehaves, contact the applications team.

Questions

How do I find the command port and command names for my camera?

Your camera's user manual lists a command port next to the image streaming settings, along with the command set and its terminator. Port numbers and command names differ by model and firmware generation, so take them from the manual for your exact model rather than from a working example written for another camera. If the manual shows no TCP command channel at all, the camera is controlled through its SDK or web interface instead.

Why does the camera answer a terminal tool but not my C# program?

Terminator first: terminal tools append CR, LF or CR LF to every line you type, while your program sends exactly the bytes you build. Append \r\n with Encoding.ASCII and retest. Second, set ReadTimeout before the first Read, or the call waits indefinitely on its default. Third, print the reply with its byte count — a GetString over a whole oversized buffer hides a truncated read behind a wall of NUL characters.

Can I send several commands over one connection?

Usually yes. Most camera command protocols accept sequential command-reply pairs on a single TCP connection, which is faster than reconnecting every cycle. Send a command, read its reply to the terminator, then send the next. Some cameras close the socket after each reply or after an idle period, so treat a zero-byte Read as a cue to reopen rather than as an error. Keep one connection per camera: two writers on one socket interleave their replies.

What read timeout should I use for a camera command?

Start at 5000 ms, which leaves roughly an order of magnitude of headroom over a typical command-to-reply cycle. Once you have measured your worst command-to-reply time over at least 100 cycles, tighten the timeout to about twice that value, and log every timeout together with the command that triggered it.

Does NetworkStream.Flush() send my command sooner?

No. A NetworkStream keeps no send buffer of its own, so Flush performs no operation on it; Write already passed the bytes to the TCP stack the moment it ran. The call is harmless, but it changes nothing, and leaving it out makes the code read honestly. If a command genuinely stalls, look at the terminator and the read loop first.