Appearance
Cookbook: SICK Inspector Results over TCP
Last updated
A SICK Inspector (or any vision sensor that sends a text result per part) decides pass or fail itself. This recipe brings those results into Scada.Ink: running counts, pass rate, a consecutive-failure alarm, a log table and a station dashboard.
Example result string, one line per inspected part:
text
PASS;12.48;25.01;0.02
FAIL;12.58;24.93;0.08Fields: result, measurement 1 (mm), measurement 2 (mm), deviation (mm). Adapt the parsing to whatever your camera sends.
How it fits together
| Part | Scada.Ink feature | Job |
|---|---|---|
| Receive | A Raw TCP device | Keeps a TCP connection to the camera and puts the latest result line into a tag |
| Process | A script with a Tag Change trigger, using a library | Parses the line, updates counters, writes a log row |
| Store | Local tags and a data table | Counters survive restarts; every result is kept |
| Alert | An alarm rule on a local tag | Three failures in a row raises a critical alarm |
| Show | A dashboard | Result, pass rate, measurements, trend, log, reset button |
Read this first: what the pieces can and can't do
- The workflow Send TCP node only sends. It never reads a reply and sets no variable, and it sends
\r\nas four literal characters. It can't be used to trigger the camera and collect the result. - Scripts have no TCP client. They can't open a socket to the camera.
- The Raw TCP driver is what can read a TCP device. It polls: on every poll it reads whatever the camera has sent (or sends a request and reads the reply). It does not act on data the moment it arrives, so a result appears in Scada.Ink up to one poll cycle later.
- The camera must be the TCP server (Scada.Ink connects to it). If your camera can only connect out to a server, this recipe won't work.
- The Raw TCP driver doesn't notice a dropped connection. After the camera restarts, click Disconnect then Connect on the device page.
Step 1: Set up the camera
In SICK's configuration software for your model:
- Configure the camera to output a result string over Ethernet after each inspection, with the camera acting as TCP server. Note the port.
- Set the output format to the fields you need, separated by
;, ending with a line break. - If the output can include a running inspection counter, add it. It makes two identical results in a row distinguishable (see "Fast lines" below).
- Trigger inspections the way the line needs — usually a photo-eye wired to the camera's trigger input.
- Note the camera's IP address, e.g.
192.168.10.50.
Menu names, the result-output port and command syntax differ between SICK models and firmware; follow SICK's manual for these settings.
Step 2: Add the camera as a Raw TCP device
- Devices → Add Device → under HTTP / Custom choose Raw TCP.
- Device Name
Inspector 1, IP Address192.168.10.50, Port the camera's result port (e.g.2006), Polling Rate (ms)200. Click Next twice (the machine details are optional), then Create Device. - Note the device id from the address bar of its page, e.g.
/devices/9. - Set its driver configuration through the API (there is no form for it — see Advanced Driver Settings for signing in and getting
$TOKEN):
bash
curl -s -X PUT "$APPLIANCE/api/devices/9" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"driverConfig":{"encoding":"ascii","read_timeout":1,"tags":[{"path":"Inspector/Result","data_type":"string"}]}}'- On the device page click Disconnect, then Connect.
- Manage Tags → Add Tag: Tag Path
Inspector/Result, Data Typestring, Display NameResult line→ Subscribe.
The tag has no request, so on each poll the driver waits up to read_timeout seconds for data from the camera:
| On a poll | Tag becomes |
|---|---|
| A result line arrived | The line, e.g. PASS;12.48;25.01;0.02, quality good |
Nothing arrived within read_timeout | Empty, quality Bad |
| Several results arrived since the last poll | All of them in one value, separated by line breaks (up to 1,024 bytes) |
So between parts the tag flips to empty/Bad. That's expected, and it's what lets the next result fire the script even when it is identical to the previous one. Don't tick History on this tag.
Fast lines
The script fires when the tag's value or quality changes. If two identical results arrive with no empty poll between them, the second is missed. Keep read_timeout well under the time between parts (here 1 s for parts every 2 s or more), or include a counter in the result string so consecutive lines always differ.
Step 3: Create the local tags
Local Tags → New Tag for each row, then Confirm.
| Tag Path | Data Type | Default Value | Persist |
|---|---|---|---|
sick/result | string | ||
sick/m1 | float | 0 | |
sick/m2 | float | 0 | |
sick/deviation | float | 0 | |
sick/total | int | 0 | ticked |
sick/pass_count | int | 0 | ticked |
sick/fail_count | int | 0 | ticked |
sick/pass_rate | float | 0 | ticked |
sick/fail_streak | int | 0 | ticked |
sick/reset_request | int | 0 |
Note the id of the Local Tags device on the Devices page; Step 8 needs it.
Step 4: Create the log table
Data Tables → New → Table name inspection_log (admin only):
| Column name | Type | Req |
|---|---|---|
result | text | ticked |
m1 | real | |
m2 | real | |
deviation | real | |
station | text |
Step 5: Put the parser in a library
- Scripts → Libraries → New Library, name
sick, Create. - Paste the code, click Save.
javascript
// Library "sick": parse one result line "PASS;12.48;25.01;0.02"
exports.parseResult = function (line) {
const parts = String(line).trim().split(';');
if (parts.length < 4) return null;
const result = parts[0].trim().toUpperCase();
if (result !== 'PASS' && result !== 'FAIL') return null;
const m1 = parseFloat(parts[1]);
const m2 = parseFloat(parts[2]);
const deviation = parseFloat(parts[3]);
if (isNaN(m1) || isNaN(m2) || isNaN(deviation)) return null;
return { result: result, m1: m1, m2: m2, deviation: deviation };
};
exports.inTolerance = function (value, nominal, tolerance) {
return Math.abs(value - nominal) <= tolerance;
};- In the Test panel type
return sick.parseResult('FAIL;12.58;24.93;0.08');and click Run Test. Returned shows the object.
A library is loaded into every script. A syntax error in it breaks every script, so test before saving, and don't declare a variable called sick in any script. See Script Libraries.
Step 6: Write the processing script
- Scripts → New Script →
SICK Result Processor→ Create. - Trigger tab: Type Tag Change, Device ID
9(the Raw TCP device), Tag PathInspector/Result. - Paste the code, toggle Enabled, click Save.
javascript
const raw = context.value;
if (!raw) return; // an empty poll (nothing arrived) also fires this trigger
const LOCAL = devices.localId();
function readNum(path) { return Number(tags.read(LOCAL, path) || 0); }
const lines = String(raw).split('\n');
for (const line of lines) {
if (line.trim() === '') continue;
const r = sick.parseResult(line);
if (!r) {
console.warn('Unrecognized result line: ' + line);
continue;
}
const pass = r.result === 'PASS';
const total = readNum('sick/total') + 1;
const passes = readNum('sick/pass_count') + (pass ? 1 : 0);
const fails = readNum('sick/fail_count') + (pass ? 0 : 1);
const streak = pass ? 0 : readNum('sick/fail_streak') + 1;
tags.write('sick/result', r.result);
tags.write('sick/m1', r.m1);
tags.write('sick/m2', r.m2);
tags.write('sick/deviation', r.deviation);
tags.write('sick/total', total);
tags.write('sick/pass_count', passes);
tags.write('sick/fail_count', fails);
tags.write('sick/fail_streak', streak);
tags.write('sick/pass_rate', Math.round((passes / total) * 1000) / 10);
await db.insert('inspection_log', {
result: r.result, m1: r.m1, m2: r.m2, deviation: r.deviation, station: 'Inspector 1',
});
}tags.writealways writes a local tag, andtags.readreturns text, hence theNumber().- A script failure shows a "Script Failed" toast and the error on the script's Log tab.
- Runs can overlap if results arrive faster than a run finishes. At normal line rates a run takes milliseconds.
Step 7: Alarm on three failures in a row
Alarms → Alarm Rules → New Rule:
| Field | Value |
|---|---|
| Name | Inspector 1 fail streak |
| Device | Local Tags |
| Tag | sick/fail_streak |
| Condition | Greater than |
| Threshold | 2 |
| Severity | Critical |
| Message (optional) | 3 or more consecutive inspection failures on Inspector 1 |
It fires on the third failure, stays active while failures continue, and clears on the next pass (the streak goes back to 0). To text someone when it fires, attach a pipeline — see the SMS recipe.
Note: A script can also raise an alarm directly with
alarms.notify('warning', 'title', 'message'), but that alarm has no rule, so it can't run a pipeline or text anyone. Use an alarm rule on a local tag when someone needs to be paged.
Step 8: A reset button operators can use
The reset goes through a local tag, so the script decides what a reset does and the button needs nothing but a tag write.
- Scripts → New Script →
SICK Reset Counters. Trigger: Tag Change, Device ID = the Local Tags device id, Tag Pathsick/reset_request.
javascript
if (context.value !== '1') return;
['sick/total', 'sick/pass_count', 'sick/fail_count', 'sick/fail_streak', 'sick/pass_rate']
.forEach(function (path) { tags.write(path, 0); });
tags.write('sick/reset_request', 0); // fires this script again; it returns at the first line
log('Inspection counters reset');- Toggle Enabled, Save.
- On the dashboard (next step) add a Button: Button Label
Reset counters, Action Write Tag, Write Tagsick/reset_request, Write Value1, Confirm before action on.
Step 9: Build the station dashboard
Dashboards → New Dashboard → Inspection Station 1. Bind each widget's value to the local tag shown.
| Widget | Title | Tag | Configuration |
|---|---|---|---|
| Status | Last result | sick/result | Delete the default rules; add = equals PASS → text PASS, green; = equals FAIL → text FAIL, red. Shape Circle |
| Value | Pass rate | sick/pass_rate | Unit %, Decimals 1, Font Size Extra Large (leave Format Automatic — Percent expects a fraction) |
| Value | Inspected | sick/total | Unit parts |
| Value | Fail streak | sick/fail_streak | Flash Alert: trigger tag sick/fail_streak, ≥ at least 3, red |
| Status | M1 12.50 ± 0.05 | sick/m1 | One rule ↔ between 12.45 / 12.55 → IN TOL, green; Default Text OUT OF TOL, Default Color red |
| Value | M1 | sick/m1 | Unit mm, Decimals 3 |
| Value | M2 | sick/m2 | Unit mm, Decimals 3 |
| Chart | M1 trend | sick/m1 | Time Window (s) 600 |
| Data Table | Last 15 inspections | none | Table Name inspection_log, Max Rows 15 |
| Button | Reset | none | As in Step 8 |
A Gauge's Warning and Critical are one-sided percentages of the scale, so they can't show a ± tolerance band; the between status rule does.
The live Chart adds a point only when the value changes. For long-term trending of M1, tick History for sick/m1 under Devices → Local Tags → Manage Tags, and use a History Chart or SPC.
Variations
| Situation | What to do |
|---|---|
| The camera answers a trigger command with the result on the same connection, and you want it to inspect continuously | Give the tag a request in the driver configuration, e.g. "request": "TRG\r\n" (JSON turns \r\n into a real CR/LF). Every poll then sends the command and reads one line — one inspection per poll, whether or not a part is there |
| The camera offers an HTTP/JSON interface | Use the HTTP / REST API driver with json_path per value — see HTTP, JSON, XML and Raw TCP devices |
| You want Scada.Ink to do the inspection from the camera's images | See Machine Vision; SICK is one of the camera types |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Inspector/Result is always empty/Bad | No driverConfig, tag path doesn't match tags[].path, wrong port, or the camera isn't sending | Check the PUT body, then Disconnect/Connect; test the port from a PC with a TCP terminal |
| Stopped updating after the camera rebooted | Raw TCP doesn't detect a closed connection | Disconnect then Connect |
| Counters jump by 2 for one part | The camera sends the result twice, or two results were read in one poll and both were processed (correct) | Check the camera output; compare with the camera's own counter |
| Identical consecutive results counted once | No empty poll between them | Lower read_timeout, or add a counter to the result string |
| Script log: Unrecognized result line | Format differs from RESULT;m1;m2;dev | Change sick.parseResult |
| Every script on the appliance fails | Error in the sick library | Fix the library; use Run Test |
| Script never runs | Wrong Device ID, not enabled, or not saved | Use the Raw TCP device's numeric id; Enabled + Save |
| Reset button: "Local tag not found" | sick/reset_request doesn't exist | Create it on the Local Tags page |