Step by step, from an empty host to the tags of an OPC UA server (DCS, PLC, Kepware, Ignition) charted live in Kymoscope.
This tutorial takes the customer's OT engineer from an empty Windows host to OPC UA tags charted live in Kymoscope, with the collector running as a service. Every step ends with a check. Allow about two hours, plus the time your IT team needs for the approvals in step 1. The reference for every OPC UA setting follows the steps.
Collect these before you begin. Most of the waiting is here, not in the installation.
Who
What you need from them
Kymoscope
An organization on the Team or Enterprise plan, and a project in it where you are an editor or a lead. An authenticator app on your phone if the project uses two-step verification (step 2).
Customer IT
A host: Windows Server 2019 or later (or Windows 10/11), with Python 3.10 to 3.13 from python.org: in the installer choose Customize installation, tick Install Python for all users and Add Python to environment variables (not the Microsoft Store Python). Outbound TCP 443 from the host to *.kymoscope.com, directly or through an HTTP proxy. A synchronised clock (NTP). If application control (WDAC, AppLocker) blocks unsigned programs, read Before installing on the Site collector page with them first.
The OPC UA server owner
The endpoint URL (for example opc.tcp://dcs-gw:4840), reachable from the host. A dedicated read-only OPC UA user. Their agreement to trust the collector's certificate (step 5). The security policy the server offers; Basic256Sha256 with SignAndEncrypt is the default.
Sign in to Kymoscope in your browser. Click your initials at the bottom left, then Organization; the Projects tab opens.
Open the project the data goes to, or click New project and give it a name (for example Customer A). Its header must say End-to-end encrypted; every project is.
Open the project's API keys tab, click Create key, name it after the host (for example Customer A collector) and click Create key.
Click Copy key, keep it in a password manager, then Done. It is shown only once.
The API key decides where the data goes: a key created here publishes into this project. The key is the one secret on the collector host: its second half (after the dot) is what your data is encrypted with and is never sent to Kymoscope.
Check: the key appears in the API keys tab, with Last used: Never.
On the collector host, open a console as administrator: right-click the Start button and choose Terminal (Admin), Windows PowerShell (Admin) or Command Prompt (Admin). Every command in this tutorial works in PowerShell and in the Command Prompt. First check Python: python --version must print 3.10, 3.11, 3.12 or 3.13. If it opens the Microsoft Store or says it is not recognised, Python is not installed as step 1 describes: install it from python.org for all users, then open a new console. Then:
Check: the last command prints kymoscope-collector and a version number. If Windows answers that the command is not recognised, the Python Scripts folder is not on PATH: reinstall Python for all users with "Add python.exe to PATH" ticked, or open a new console.
Check the network from the same host, in PowerShell (both must say TcpTestSucceeded : True):
Use your OPC UA server's host name and port in the second line. If the first one fails, the customer's firewall or proxy blocks Kymoscope (see Proxy on the Site collector page); if the second fails, the host cannot reach the OPC UA server.
Behind an HTTP proxy
If the host reaches the internet only through a proxy, give it to pip on every install line (and later to the collector, with proxy in collector.yaml; see Proxy on the Site collector page):
Download the packages on another PC and copy them over:
Shell
# on a PC with internet, same Windows and the same Python version as the collector hostpip download "kymoscope-collector[windows]"-d wheels
# copy the wheels folder to the collector host, thenpip install --no-index--find-links wheels "kymoscope-collector[windows]"
Create the folder, and keep the secrets in files that only administrators and the service can read, so collector.yaml itself holds no secret and can be reviewed or versioned.
Shell
mkdir C:\ProgramData\Kymoscope
cd C:\ProgramData\Kymoscope
notepad api-key.txt
notepad opcua-password.txt
Notepad asks whether to create each file: click Yes. In api-key.txt, paste the API key from step 2; in opcua-password.txt, the OPC UA user's password. Save and close. Then make them readable only by administrators and the service (the *S-1-5-... names are the Administrators group and the SYSTEM account, and work on Windows in any language):
Each line answers Successfully processed 1 files (in a Windows in another language, the same sentence in that language). Then create the configuration with notepad collector.yaml, paste this, adapt it and save. Keep the indentation exactly as shown, with spaces (not tabs): it is part of the format.
collector.yaml
# C:\ProgramData\Kymoscope\collector.yaml
api_key: file:api-key.txt
state_dir: C:/ProgramData/Kymoscope/state
sources:
- name: Customer A DCS # the stream name you will see in Kymoscope
type: opcua
url: opc.tcp://dcs-gw:4840 # from the OPC UA server's owner
security: Basic256Sha256
mode: SignAndEncrypt
user: kymo-reader # omit user and password for anonymous access
password: file:opcua-password.txt
tags: tags.csv
name is the stream name in Kymoscope. Use one people will recognise.
security and mode must match an endpoint the server offers. kymoscope-collector endpoints --url opc.tcp://dcs-gw:4840 lists them as JSON and names the strongest in strongest; only_none: true means the server offers no encryption at all.
tags.csv does not exist yet; step 6 creates it.
Check: nothing to run yet. A mistake in this file is reported in one sentence naming the key as soon as the next step reads it.
With encryption on, the server and the collector each prove who they are with a certificate. Create the collector's certificate (valid ten years) and note its thumbprint:
Shell
# create the collector's own certificate (only the first time) and show where it is and its thumbprint
kymoscope-collector cert --config C:\ProgramData\Kymoscope\collector.yaml
# it prints:# Certificate: C:\ProgramData\Kymoscope\state\pki\own\cert.der# Thumbprint: 26bb7433ee93a08b8da42d9ee9dd8828293ecb9e# Days left: 3650
Give state\pki\own\cert.der and the thumbprint to the server owner, and ask them to add it to the server's trusted clients. Servers often show the thumbprint in capitals or with colons (26:BB:74:...); the characters are the same. In Kepware this is the OPC UA Configuration Manager, Trusted Clients tab, Import. In Ignition, the gateway's OPC UA security page lists client certificates to trust. Other servers have an equivalent trust list. A server that asks for a PEM file (.pem or .crt) takes the same certificate converted with certutil -encode state\pki\own\cert.der cert.pem.
Alternatively, run the browse command of step 6 once. It stops with Cannot browse ...: does not trust this collector's certificate yet (BadCertificateUntrusted), and the server lists the certificate as rejected or quarantined. The server owner compares the thumbprint with the one above and trusts it from there.
The collector trusts the server's certificate the first time it connects and refuses a different one later at the same URL. Credentials are only sent after that check. See Reference: certificates, below.
Check: the server owner confirms the certificate is trusted.
The collector reads only the tags listed in tags.csv, the file named by tags: in collector.yaml. The file does not exist yet: you create it in this step. The collector never creates or fills it by itself, and never adds a tag the server offers later. A tag that is not in the file is not read.
There are two ways to create it. Use the first when you want to see what the server offers; use the second when you already know which tags you want.
Option A: let the collector list the server's tags
The browse command connects to the server once, walks its address space, writes every readable tag to tags.csv and exits. It sends nothing to Kymoscope and changes nothing on the server.
Shell
# connect to the server, list every readable numeric tag and write them to tags.csv (nothing is sent to Kymoscope)
kymoscope-collector browse --url opc.tcp://dcs-gw:4840 --config C:\ProgramData\Kymoscope\collector.yaml --out C:\ProgramData\Kymoscope\tags.csv
# it prints:# Wrote 1214 tags to C:\ProgramData\Kymoscope\tags.csv; skipped 87 (61 strings or other types, 26 arrays)
It writes numeric, boolean and enumeration tags. Strings, arrays and tags the OPC UA user may not read are skipped and counted in the last line.
The name of each tag is its path in the server, joined with dots (for example Area1.FIC101.PV). A name that would repeat gets _2, _3...
browse overwrites the file given in --out. Once you have edited tags.csv, run it again into another file (for example --out C:\ProgramData\Kymoscope\all-tags.csv) and copy the rows you want from there.
Keep only the tags you want
browse lists everything; the collector reads only what stays in the file. Open tags.csv in Excel or another spreadsheet, delete the rows you do not want and save it as CSV. Any of Excel's CSV options works, including the semicolon-separated one Excel uses in Spanish and other languages. Keep the node_id column as it is; rename the name column freely, but keep names unique.
Limit the walk to one area of the server
On a large server, start the walk at the folder or object that holds the tags you want, with --root, instead of at the top of the address space:
Shell
# list only the tags below one folder of the server, at most 3 levels down
kymoscope-collector browse --url opc.tcp://dcs-gw:4840 --config C:\ProgramData\Kymoscope\collector.yaml --root"nsu=urn:customer:dcs;s=Area1"--depth 3 --out C:\ProgramData\Kymoscope\tags.csv
--root takes the node id of a folder or object, as an OPC UA client such as UaExpert shows it (the NodeId attribute), or as the server's own configuration tool names it. --depth is how many levels below it to walk (10 if you leave it out).
Option B: write tags.csv yourself
When you already know the tags, from the DCS or PLC configuration or a historian export, write the file directly, in Notepad (notepad C:\ProgramData\Kymoscope\tags.csv) or a spreadsheet saved as CSV. The first line names the columns; then one line per tag:
tags.csv
node_id,name,group,sampling_ms,unit,description
"nsu=urn:customer:dcs;s=FIC101.PV",FIC101,,,m3/h,Feed flow
"nsu=urn:customer:dcs;s=TIC201.PV",TIC201,,1000,degC,Reactor temperature
node_id is the only required column: the tag's node id, written nsu=<namespace URI>;s=<identifier> (i=, g= and b= identifiers work too). Use the namespace URI, not its number (ns=2): the number can change when the server restarts. An OPC UA client such as UaExpert shows both the NodeId and the server's namespace URIs (in the Server object, NamespaceArray).
name is the channel name in Kymoscope; leave it empty to use the node identifier.
group, sampling_ms, unit and description are optional and may stay empty, or be left out. Columns can be in any order. Every column is described under Reference: tags.csv, below.
Sampling rates
Leave sampling_ms empty and each tag is sampled at the fastest interval the server states for it. The group column shows that interval in milliseconds. Where group is empty, the server states none, and the collector samples that tag once a second (default_sampling_ms). If such a tag changes faster and you need every change, put its interval in sampling_ms (for example 100), or set default_sampling_ms on the source. Agree on fast rates with the server owner: they load the server.
Changing the list later
Add, remove or rename rows at any time, with the collector running. It checks the file every 10 seconds and applies a change by itself; there is nothing to restart. A file with an error is ignored, the previous list stays in use and the log says what is wrong. One source handles about 2,000 tags comfortably; for more, split them into several tags files, one source each.
Check:tags.csv lists the tags you want, one per line. With option A, the command printed Wrote N tags; if it printed Cannot browse, the rest of the sentence says what to do: the certificate is not trusted yet (step 5), the user name or password is wrong, the security or mode does not match the server, or the server cannot be reached. A node_id the server does not know shows in the next step's log (tag ... cannot be monitored); the other tags keep working.
Run the collector in the console first, so you can read its log (each line starts with the date and time):
Shell
# start the collector in this console; stop it with Ctrl+C
kymoscope-collector run --config C:\ProgramData\Kymoscope\collector.yaml
# it prints (the date and time are left out here):# INFO Customer A DCS: Waiting for a project member to approve this sender: open Kymoscope on any member's browser# INFO stream 'Customer A DCS' ready# WARNING stream 'Customer A DCS': waiting for a project member to approve this sender; data waits in memory# INFO source 'Customer A DCS': connected to opc.tcp://dcs-gw:4840 (1000 tags)
The first run waits for approval: a new sender must be admitted to an end-to-end encrypted project before it receives the project key. Open the project in Kymoscope in your browser (any project member whose browser already opens the project). The browser checks that the collector holds the API key and admits it by itself within a few seconds; there is nothing to click. The console then prints INFO Customer A DCS: Approved: publishing with the project key, and the data that waited in memory is sent.
In the project's API keys tab, the key now shows Sender · Active.
Open the workspace at app.kymoscope.com, click LIVE in the left rail and click Connect next to Customer A DCS. Close the panel.
Open Charts and choose Multi-Series. Right-click the new chart and choose Multiband settings. X signal is already Time. Click Add signal: a row appears with a tag in its Signal box; click that box and type or pick the tag you want. Repeat for each tag, then close the settings: the values chart live, with the time of day on the axis.
Leave the collector running and go on, or stop it with Ctrl+C.
Check: values move in the chart and match what the operators see on the DCS. The time stamps are the server's own source times.
Stop the console run with Ctrl+C if it is still running, then, from the same administrator console:
Shell
kymoscope-collector service install --config C:\ProgramData\Kymoscope\collector.yaml
kymoscope-collector service start --config C:\ProgramData\Kymoscope\collector.yaml
The service KymoscopeCollector starts automatically with Windows (delayed start) and restarts itself after a failure. It runs as LocalSystem, which can read the secret files. Linux and Docker are on the Site collector page.
Under a dedicated account (optional)
Many IT teams prefer a dedicated local account to LocalSystem. After the two commands above, from the same administrator console (choose a long password):
Shell
kymoscope-collector service stop --config C:\ProgramData\Kymoscope\collector.yaml
net user kymoscope "<a long password>" /add /y
sc.exe config KymoscopeCollector obj= .\kymoscope password= "<the same password>"
icacls C:\ProgramData\Kymoscope /grant "kymoscope:(OI)(CI)M"
icacls C:\ProgramData\Kymoscope\state\streams /grant "kymoscope:(OI)(CI)M"
icacls C:\ProgramData\Kymoscope\api-key.txt /grant kymoscope:R
icacls C:\ProgramData\Kymoscope\opcua-password.txt /grant kymoscope:R
icacls C:\ProgramData\Kymoscope\state\pki\own\key.pem /grant kymoscope:R
kymoscope-collector service start --config C:\ProgramData\Kymoscope\collector.yaml
Write sc.exe, not sc: in PowerShell sc means something else.
/y answers the question net user asks about passwords longer than 14 characters.
Give the account the right to run services: open secpol.msc, Local Policies, User Rights Assignment, Log on as a service, and add kymoscope. Without it the service does not start (error 1069).
Python must be installed for all users (step 1); the account cannot read a Python under C:\Users.
With recording on (step 10), also run the state\streams line for state\history.
Check:services.msc shows the service Running, and the stream in Kymoscope keeps updating. Restart the host once during commissioning and confirm the data comes back on its own.
The health stream reports the collector's own state every 5 seconds as a normal stream: chart it, and set alerts on it so you hear about problems before the users do. Add these two lines at the end of collector.yaml:
collector.yaml
health:
stream: Customer A collector health
Restart the service so it reads the change:
Shell
kymoscope-collector service stop --config C:\ProgramData\Kymoscope\collector.yaml
kymoscope-collector service start --config C:\ProgramData\Kymoscope\collector.yaml
The health stream is a new sender, so open the project in Kymoscope once more to approve it, as in step 7. Then connect it from LIVE like the data stream.
Channel
Alert when
Customer A DCS.connected
0 for more than a minute: the OPC UA server is unreachable or refuses the collector.
Customer A DCS.overflows
Rises: the server drops values because it cannot keep up. Remove tags or slow some down.
Customer A DCS.server_cert_rejected
1: the server presented a new certificate. Check it and trust it (see Reference: certificates).
Customer A DCS.own_cert_days_left
Below 60: plan the certificate renewal with the server owner.
Customer A DCS.values_per_s
Drops far below its usual level: values stopped arriving.
Customer A DCS.buffer_mb
Rises: Kymoscope is unreachable and data waits on disk (it is sent when the connection returns).
Customer A DCS.buffer_dropped
Above 0: the outage outlasted the disk buffer. Data was lost.
disk_free_gb
Below a few GB.
During a network outage the collector keeps acquiring and stores the encrypted data on disk (up to 10 GB per stream by default). When the connection returns, live data goes first and the backlog follows.
Gaps from the server's own history. If the OPC UA server keeps history (Historical Access), the collector can fill the time it could not reach the server, or a period before it was installed. See OPC UA history.
Keep years of every value. The Historian comes with Enterprise (5 years of raw data, 5,000 channels); on other plans ask Kymoscope to turn it on for your organization. Install the history extra (pip install "kymoscope-collector[windows,history]", with the service stopped) and restart the collector. Until then the log says the source records manually, which is expected. With the extra, the source records always by default; set record: manual on it to store nothing by itself, or record only while a condition holds with record: trigger. Add a recording: block with the site's zone so days are cut at its midnight:collector.yaml (end)
recording:
timezone: Europe/Madrid
See Recording. The day files are stored by Kymoscope by default; on Enterprise they can stay in your own S3-compatible bucket instead (AWS S3, Cloudflare R2, Backblaze B2, Wasabi, Hetzner, OVH, or MinIO on your own network), so the history stays in your infrastructure: see Your own storage. Either way they are encrypted before they leave the collector.
Mark moments from the DCS. Set mark_tag on the source to the name of a tag in tags.csv, for example an operator push-button. Each rise of that tag puts a 1 in the stream's mark channel.
Cut batches into cycles, 24/7, and get an email when one runs late. With the source recording by itself, a cycles: section cuts it as values arrive, with phases and limits, and alerts: emails when a cycle or phase misses a limit:collector.yaml (end)
To update, stop the service first (pip cannot replace files the running service holds), then, from an administrator console:
Shell
kymoscope-collector service stop --config C:\ProgramData\Kymoscope\collector.yaml
pip install -U"kymoscope-collector[windows]"
kymoscope-collector service start --config C:\ProgramData\Kymoscope\collector.yaml
The identity, certificates and buffer in C:\ProgramData\Kymoscope\state survive the update: no new approval, and data collected meanwhile is sent afterwards. To remove the collector: kymoscope-collector service stop, then service remove with the same --config, then pip uninstall kymoscope-collector.
"does not trust this collector’s certificate yet (BadCertificateUntrusted)"
The server does not trust the collector yet: step 5. After cert --renew it must be trusted again.
"refused the user name or password (BadUserAccessDenied)"
Check user in collector.yaml and the password in opcua-password.txt with the server owner.
"offers no endpoint with the security and mode set in collector.yaml"
Run kymoscope-collector endpoints --url with the server's address, and set security and mode to its strongest. If it says only_none, the server offers no encryption: ask its owner to enable one, or accept that with security: None and allow_insecure: true.
"cannot reach opc.tcp://..."
Wrong host name or port, the server is down, or a firewall blocks it. Check with Test-NetConnection (step 3).
The log names a file in pki\rejected
The server presented a certificate the collector does not know. Check its thumbprint with the server owner, then move the file to pki\trusted. No restart needed.
"tag X cannot be monitored: BadNodeIdUnknown" (or another Bad... code)
The node id in tags.csv is wrong, its namespace is not on the server, or the user may not read it. Check the node with browse; the other tags keep working.
Values are missing or sparse
Look at overflows and revised. Rising overflows: the server's queue drops values. Non-zero revised: the server chose a slower sampling interval than requested (the log shows an example). Remove tags, set a slower sampling_ms on some, or raise the limit on the server.
clock_offset_s is large
The collector and the server disagree on the time. Fix NTP on whichever side is wrong. A rising server_time_fallback means the server sends no source timestamps.
"security None sends data unprotected"
Set security and mode back to the defaults, or accept the risk with allow_insecure: true.
Create key is greyed out: "Your access to the project key is still pending"
The project requires two-step verification: click Set up two-step verification on the project page (step 2).
"Waiting for a project member to approve this sender" does not go away
Open the project in Kymoscope as a member whose browser already opens it. If the API keys tab shows the key as Needs reissue, reissue it and put the new key in api-key.txt.
The API key in api-key.txt is wrong or was revoked. Paste the key again, or create a new one (step 2).
"stream ... cannot start" with a connection error
The host cannot reach Kymoscope on port 443. Check with Test-NetConnection (step 3); fix the firewall or set proxy in collector.yaml (see Proxy on the Site collector page).
"WinError 32" while upgrading
Stop the service first, upgrade, start it again.
More, for every source type, under Troubleshooting on the Site collector page.
Required. The endpoint, for example opc.tcp://dcs-gw:4840.
tags
Required. Path to the tag list (tags.csv), relative to collector.yaml.
security
Basic256Sha256 (default), Aes128_Sha256_RsaOaep, Aes256_Sha256_RsaPss or None.
mode
SignAndEncrypt (default) or Sign.
allow_insecure
Default false. Must be true to use security: None; without it the collector refuses to start. A password with security: None travels unencrypted (a warning is logged).
user
Optional user name. Use a dedicated read-only account.
password
Optional. Accepts env:NAME and file:PATH like api_key.
trust_server
first_use (default) or pinned. See Reference: certificates.
publishing_ms
Subscription publishing interval. Default 500. A group of slower tags uses its own, slower interval.
default_sampling_ms
Sampling interval for a tag whose server states no minimum. Default 1000.
node_id,name,group,sampling_ms,unit,description
"nsu=urn:customer:dcs;s=FIC101.PV",FIC101,,,m3/h,Feed flow
"nsu=urn:customer:dcs;s=TIC201.PV",TIC201,,1000,degC,Reactor temperature
Column
Meaning
node_id
Required. The node id in namespace-URI form: nsu=<namespace URI>;s=<identifier> (i=, g= and b= identifiers work the same way). The namespace index is looked up on every connection, so the file survives a server restart that renumbers namespaces.
name
Channel name in Kymoscope. Must be unique in the file. Empty: taken from the node identifier.
group
Written by browse as the tag’s rate in milliseconds. Informational; not read.
sampling_ms
Optional. Empty: the fastest interval the server states for the node (shown in group), or default_sampling_ms (1 s) when it states none. Set it to sample a tag faster than that default, or to slow down a tag that would overload the server.
unit
Optional override. Empty: the server’s engineering unit.
description
Optional text.
Columns may be in any order; other columns are ignored. The file is checked every 10 seconds; a change re-subscribes the source by itself (its tags have a short gap while it does). A file with an error is ignored and the previous list stays in use (the log says why).
browse writes every readable numeric, boolean or enumeration variable; strings, arrays and variables without read access are skipped and counted. Without --config it connects with no security; with one, it uses the OPC UA source for that URL and its certificates.
The collector keeps its own certificate and private key in state_dir/pki/own(kymoscope-collector cert prints the path, the SHA-1 thumbprint and the days left).
first_use (default): the first server certificate seen at a URL is stored in pki/trusted; a different one at that URL is refused afterwards.
pinned: only certificates already in pki/trusted are accepted. Copy the server's certificate there as <sha1>.der for strict change control.
Refused certificates land in pki/rejected/<sha1>.der and the log names the file. Once checked with the server owner, move it to pki/trusted; the collector retries every 60 seconds.
Renewal:kymoscope-collector cert --config collector.yaml --renew keeps the old pair in pki/own/old-YYYYMMDD-HHMMSS and prints the new thumbprint. The server must trust the new certificate before the collector connects again: do it in a maintenance window and restart the collector afterwards.