Install and check a license
The PDComm SDK libraries check the license when you start new communication. Without a valid license, or after the evaluation period, they don’t start the new communication and return a dedicated error instead. Communication that is already running keeps working.
A license comes as one text file. Put the file where your application can find it, or install it through the API.
What needs a license
Section titled “What needs a license”Only the SDKs need a license. A license grants features per module.
| Component | Module | License |
|---|---|---|
| SECS/GEM SDK | secs-gem; the GEM300 capability catalog uses gem300 |
Required, bound to host ids |
| Modbus SDK | modbus; Modbus/TCP Security also needs modbus-security |
Required |
| OPC UA SDK | opcua-client, opcua-server (PubSub included) |
Required |
| MQTT | mqtt |
Defined as a license module. You use MQTT through the OPC UA simulator programs, which need no license. |
| Simulators (GUI, headless, MCP), virtual serial port tools, docs CLI | None | Not needed. They’re free. |
The SECS/GEM simulator (GUI, headless, and MCP) and the Modbus simulator GUI use the SDK libraries, so they embed a tool license that works only for those programs and install it at startup. You don’t install anything for them. In the GUIs, the About window in the Help menu shows the license status and the host id.
License kinds
Section titled “License kinds”Kind (kind) |
Purpose | Validity | Host id |
|---|---|---|---|
evaluation |
30-day evaluation | 30 days from issue | Not bound |
developer |
Developing and distributing applications | No expiry; has a maintenance end (updatesUntil) |
Only the SECS/GEM modules are bound to the host ids of development PCs. |
runtime |
Running SECS/GEM on one piece of equipment | As agreed | Bound to the host id of the equipment PC |
tool |
Embedded in the PDComm simulators | No expiry | Not bound; works only for the named programs |
Evaluation
Section titled “Evaluation”An evaluation license grants every module for 30 days. Features, run time, and the number of runs aren’t limited. When the period ends, the state becomes expired and new communication doesn’t start. The days_left field of the status JSON shows the days that remain.
Developer licenses and distribution (OPC UA, Modbus, MQTT)
Section titled “Developer licenses and distribution (OPC UA, Modbus, MQTT)”Applications you build with a developer license for OPC UA, Modbus, or MQTT are distributed royalty-free. Ship the license text with your application in one of two ways:
- Have your application install the license text through the API at startup. Compile the text into the executable or ship it as a resource.
- Put the license file next to your executable under the name
pdcomm.license.
SECS/GEM runtime licenses per equipment
Section titled “SECS/GEM runtime licenses per equipment”The SECS/GEM modules (secs-gem, gem300) are bound to host ids. A developer license lists the host ids of your development PCs, and a runtime license lists the host id of the equipment PC. Evaluation licenses aren’t bound to a host id.
For each piece of equipment:
- On the equipment PC, run
pdcomm-license host-idor read the host id with the host id API. - Send the host id to receive a runtime license.
- Save the file as
pdcomm.licensenext to the application on the equipment PC, or install it another way.
Maintenance end
Section titled “Maintenance end”updatesUntil in a developer license is the end of maintenance. If the SDK was built (UTC date) after that date, the state is updates-expired. SDKs built on or before that date keep working. Renew maintenance to move to a newer SDK. The SDK build date is in the output of pdcomm-license version and in build_date of the status JSON.
Host ids
Section titled “Host ids”A host id looks like PDH1-XXXX-XXXX-XXXX-XXXX, where X is an upper-case hexadecimal digit. It’s the first 8 bytes of a SHA-256 hash of the machine id that the operating system records, so the original id can’t be recovered from it.
| Operating system | Source id | How to see it yourself |
|---|---|---|
| Linux | /etc/machine-id, or /var/lib/dbus/machine-id when that’s missing |
cat /etc/machine-id |
| Windows | MachineGuid under the registry key HKLM\SOFTWARE\Microsoft\Cryptography |
reg query HKLM\SOFTWARE\Microsoft\Cryptography /v MachineGuid |
| macOS | The hardware UUID (gethostuuid()) |
IOPlatformUUID in ioreg -rd1 -c IOPlatformExpertDevice |
Read the host id in any of these ways. They all return the same value.
| Way | Usage |
|---|---|
| Command | pdcomm-license host-id |
| C (SECS/GEM) | pdcomm_license_host_id |
| C (OPC UA) | pdcomm_opcua_license_host_id |
| C++ | pdcomm::License::hostId(), pdcomm::modbus::License::hostId(), pdcomm::opcua::license::host_id() |
| .NET | PDComm.License.HostId(), PDComm.Modbus.License.HostId(), PDComm.OpcUa.License.HostId() |
| Modbus JSON | {"action":"host_id"} returns {"host_id":"PDH1-…"} |
The host id changes in these cases. When it changes, get a license reissued for the new host id.
- You reinstalled the operating system. Linux creates a new
machine-idand Windows a newMachineGuid. - The logic board of a Mac was replaced.
- You cloned a virtual machine. Clones can also end up with the same id.
Containers often have no /etc/machine-id. Then the host id can’t be read: pdcomm-license host-id ends with a host id unavailable error, and licenses bound to host ids give host-mismatch. Create a fixed /etc/machine-id in the container, or mount the host’s file read-only.
License files
Section titled “License files”A license file is a text file with one or more blocks like this one:
-----BEGIN PDCOMM LICENSE-----eyJ2IjoxLCJpZCI6ImxpY18…(shortened)….Fq9cz…(shortened)-----END PDCOMM LICENSE------ The contents of a block are signed. If one character changes, the block is rejected.
- Text outside blocks is ignored, so you can add notes. Both LF and CRLF line endings work.
- You can put several licenses into one file, for example a developer license and a runtime license in one
pdcomm.license. - A file can’t be larger than 1 MiB.
Where to put a license
Section titled “Where to put a license”The libraries read every one of these locations and combine the licenses they find. A license in one location doesn’t hide licenses in another. When several licenses grant the same module, a license that isn’t an evaluation wins.
| Order | Location | Notes |
|---|---|---|
| 1 | Text installed through the API | See Install through the API. |
| 2 | Environment variable PDCOMM_LICENSE |
The license text itself |
| 3 | Environment variable PDCOMM_LICENSE_FILE |
Path of a license file |
| 4 | pdcomm.license next to the executable, or else next to the SDK library |
For distributing with your application |
| 5 | pdcomm.license in the user configuration folder |
See the table below. |
| 6 | pdcomm.license in the system configuration folder |
See the table below. |
| Operating system | User configuration folder | System configuration folder |
|---|---|---|
| Linux | $XDG_CONFIG_HOME/pdcomm/, or ~/.config/pdcomm/ when the variable isn’t set |
/etc/pdcomm/ |
| macOS | ~/Library/Application Support/PDComm/, or else $XDG_CONFIG_HOME/pdcomm/ or ~/.config/pdcomm/ |
/etc/pdcomm/ |
| Windows | %APPDATA%\PDComm\ |
%ProgramData%\PDComm\ |
- In each location only the first file found is used. For example, when there’s a file next to the executable, the folder of the library isn’t checked.
- An environment variable with an empty value counts as unset.
- The libraries read the files again on every check. When you replace a file, the next new communication uses it without a restart.
- When the file that
PDCOMM_LICENSE_FILEnames can’t be read, the error isn’t ignored: it appears insourcesof the status JSON.
# Applies only to programs started from this shell.export PDCOMM_LICENSE_FILE="$HOME/pdcomm/pdcomm.license"
# Applies to every program of this user (Linux).mkdir -p ~/.config/pdcommcp pdcomm.license ~/.config/pdcomm/pdcomm.license# Applies only to programs started from this PowerShell.$env:PDCOMM_LICENSE_FILE = "$HOME\pdcomm\pdcomm.license"
# Applies to every program of this user.New-Item -ItemType Directory -Force "$env:APPDATA\PDComm" | Out-NullCopy-Item pdcomm.license "$env:APPDATA\PDComm\pdcomm.license"Install through the API
Section titled “Install through the API”Text you install through the API applies only to the library whose API you called. If one process uses the SECS/GEM, Modbus, and OPC UA libraries together, install the license in each. Environment variables and files are read by every library.
| Language | SECS/GEM | Modbus | OPC UA |
|---|---|---|---|
| C | pdcomm_license_install |
JSON action license_install |
pdcomm_opcua_license_install |
| C++ | pdcomm::License::install (pdcomm_license.hpp) |
pdcomm::modbus::License::install (pdcomm/modbus/pdcomm_modbus_license.hpp) |
pdcomm::opcua::license::install (pdcomm/opcua/pdcomm_opcua.hpp) |
| .NET | PDComm.License.Install |
PDComm.Modbus.License.Install |
PDComm.OpcUa.License.Install |
- Installing again replaces the text installed earlier through the API. Installing empty text removes it.
- Text without any block whose signature verifies is refused, and the text installed earlier stays. C (SECS/GEM) returns
PDCOMM_INVALID_DATA, and Modbus returns the error codelicense_invalid. Text larger than 1 MiB is an invalid argument. - An expired license is still installed when its signature verifies. Check whether it’s valid with the status, not with the install result.
/* SECS/GEM: install once before you start communication. */static const char license_text[] = "-----BEGIN PDCOMM LICENSE-----\n…\n-----END PDCOMM LICENSE-----\n";PdcommStatus status = pdcomm_license_install((const uint8_t *)license_text, sizeof license_text - 1);if (status != PDCOMM_OK) { /* PDCOMM_INVALID_DATA: no license with a valid signature. */}// .NET (SECS/GEM): install the license file shipped with the application.PDComm.License.Install(File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "app.license")));{"action":"license_install","text":"-----BEGIN PDCOMM LICENSE-----\n…\n-----END PDCOMM LICENSE-----\n"}{"ok":true,"result":{"installed":1}}installed is the number of blocks whose signature verifies.
Checked calls and what happens when they’re refused
Section titled “Checked calls and what happens when they’re refused”The license is checked only by calls that start new communication. Arguments are checked first, so invalid arguments return an argument error regardless of the license.
| Product | Checked calls | Module | When refused |
|---|---|---|---|
| SECS/GEM | pdcomm_tcp_create, pdcomm_tcp_start, pdcomm_secs1_create, pdcomm_secs1_start |
secs-gem |
C returns PDCOMM_LICENSE_REQUIRED (16), C++ throws pdcomm::Error with status 16 (its what() starts with PDComm license required), and .NET throws PdcommException whose Status is LicenseRequired. |
| SECS/GEM | pdcomm_gem300_capability_catalog_create, pdcomm_gem300_capability_catalog_replace |
gem300 |
Same as above |
| Modbus | JSON action connect |
modbus, plus modbus-security when the configuration has security |
PDCOMM_MODBUS_SERVICE_ERROR (5) with the error code license_required |
| OPC UA | pdcomm_opcua_discover, pdcomm_opcua_client_connect |
opcua-client |
Status 11 with an error text |
| OPC UA | pdcomm_opcua_server_create |
opcua-server |
Same as above |
Endpoints you already created, established connections, and running servers keep working when the license expires or changes. The license is checked again when you stop and restart an endpoint or connect again.
The refusal message looks like this. The state is in parentheses.
PDComm license required for module 'secs-gem' (missing): no valid license for this module was found. Existing connections keep running. Install a license with the license API, PDCOMM_LICENSE_FILE or a pdcomm.license file.Modbus puts a code and the license state into the error reply:
{"code":"license_required","error":"PDComm license required for module 'modbus' (expired): license lic_… has expired. …","license":{"license_id":"lic_…","module":"modbus","state":"expired"},"ok":false}The SECS/GEM C ABI has no last-error text. When you get status 16, use the status API to see why.
Check the status
Section titled “Check the status”| Language | SECS/GEM | Modbus | OPC UA |
|---|---|---|---|
| C | pdcomm_license_status |
JSON action license_status |
pdcomm_opcua_license_status |
| C++ | pdcomm::License::status() |
pdcomm::modbus::License::status() |
pdcomm::opcua::license::status() |
| .NET | PDComm.License.Status() |
PDComm.Modbus.License.Status() |
PDComm.OpcUa.License.Status() |
The C functions copy JSON text into the caller’s buffer. When the buffer is too small, they leave it unchanged and report the size they need. .NET returns a JsonElement. The main fields of the status JSON are:
| Field | Contents |
|---|---|
build_date |
Build date of this SDK (UTC) |
host_id, host_id_error |
The host id of this computer; null when it can’t be read, with the reason in host_id_error |
program |
Executable name (lower case, without extension) |
keys |
Signing keys this SDK verifies |
sources |
Locations found (source, location), block count (blocks), and read errors (error) |
licenses |
The contents and verification result of each block (verification: valid, malformed, unknown-key, or invalid-signature) |
modules |
For each module: state, granted, days_left, license_id, kind, expires_at, rejected, and host_bound |
source in sources is one of api, env:PDCOMM_LICENSE, env:PDCOMM_LICENSE_FILE, executable-dir, library-dir, user-config, and system-config.
The pdcomm-license command
Section titled “The pdcomm-license command”The SDK packages contain the pdcomm-license command for checking licenses.
| Command | What it does |
|---|---|
pdcomm-license host-id |
Prints the host id of this computer. |
pdcomm-license inspect <file> [--json] |
Shows the signature check and the contents of each block in the file. |
pdcomm-license verify <file> [--module <module>]... [--program <name>] [--json] |
Shows the state of each module for this computer, the current time, and this SDK build. |
pdcomm-license status [--json] |
Shows the state from the licenses found in environment variables and configuration folders. |
pdcomm-license version |
Prints the tool version, the build date, and the keys it verifies. |
The exit status is 0 for success, 1 when not granted or no valid license was found, and 2 for usage or file errors. With --module, verify exits with 0 when every named module is granted; without it, when any module is granted.
$ pdcomm-license verify pdcomm.license --module secs-gem --module modbussecs-gem: evaluation (25 days left) [lic_…]modbus: evaluation (25 days left) [lic_…]status shows the view of pdcomm-license itself. It sees the same environment variables and configuration folders as your application, but not text your application installed through the API or files next to your application’s executable. Use the status API to see what your application sees.
Troubleshooting
Section titled “Troubleshooting”| State | Meaning | What to do |
|---|---|---|
missing |
No valid license grants the module. | Check that the license file appears in sources of the status JSON and that the module is in the license’s modules. If rejected is set, see the rejection table below. |
expired |
The license expired (expiresAt). An evaluation reaches this state after 30 days. |
Get a developer license or a new license. |
not-yet-valid |
The license isn’t valid yet (notBefore). |
Check the clock and time zone of the computer. |
updates-expired |
The SDK was built after the maintenance end (updatesUntil). |
Use an SDK built before the maintenance end, or renew maintenance. |
program-mismatch |
A tool license doesn’t cover this program. | Tool licenses are only for the PDComm simulators. Use an evaluation or developer license for your application. |
host-mismatch |
A license bound to host ids (SECS/GEM developer or runtime) doesn’t list this computer. | Send the output of pdcomm-license host-id to get the license reissued. If the host id can’t be read, see Host ids. |
A rejected block grants nothing. The state is then missing, and the reason appears in rejected under modules and in verification under licenses.
| Rejection | Meaning | What to do |
|---|---|---|
malformed |
The block can’t be parsed: characters were lost while copying, or the format isn’t supported. | Copy the file you received again, as is. Copy the whole block, including the first and last lines. |
unknown-key |
The block is signed with a key this SDK doesn’t know. | A test license was used with a released SDK, or the license was issued with a newer key. Get the license reissued or update the SDK. |
invalid-signature |
The signature doesn’t match: the contents of the block changed. | Make sure nobody edited the block, and use the original file again. |
To find out which location a license came from, set PDCOMM_LICENSE_NO_SEARCH=1. The libraries then read only the text installed through the API, PDCOMM_LICENSE, and PDCOMM_LICENSE_FILE, and don’t search the file locations (4 to 6).
PDCOMM_LICENSE_NO_SEARCH=1 PDCOMM_LICENSE_FILE=./pdcomm.license pdcomm-license status