Skip to content

Your First Device: End to End

This page walks you through a complete data path with the built-in virtual driver: log in to get a token, create a profile, point, and device, configure the point's attributes, then read live values and send a write command. Each step has copy-paste commands and a "what you should see" block, so you can follow along directly.

You are here: you've started the dependency stack and read the core concepts. By the end of this page you'll have a device connected through the virtual driver, producing live point values you can read, with write commands flowing back to its writable points.

What This Path Looks Like

The golden path is a chain of HTTP calls with front-to-back dependencies, all going through the gateway dc3-gateway (:8000). The first two steps get a token. The next four build metadata in the Manager Center. The last two read values and send commands through the Data Center. Keep this map in mind — each step below tells you where you are in it.

Conventions

All id values, tokens, and return values below are examples. Your environment generates snowflake IDs (long strings of digits), so replace them with the real values returned by the previous step. Every write endpoint returns the unified envelope { "ok": true, "code": "...", "message": "...", "data": "..." }, and the new entity's ID sits in the data field.

Step 0: Start the Stack

Bring up the database, message queue, and development stack. These two commands start PostgreSQL + RabbitMQ, then the gateway, the four centers, and the drivers (the virtual driver starts with the stack).

bash
make up-db && make up-dev

What you should see: podman ps lists dc3-postgres, dc3-rabbitmq, dc3-gateway, dc3-center-auth/manager/data/agentic, and several dc3-driver-* containers as running. The gateway is reachable at http://localhost:8000.

Point the dc3 CLI at the Gateway First

If you use the dc3 CLI, tell it the gateway address first (once is enough): dc3 config set gateway http://localhost:8000.

Steps 1–2: Log In for a Token

Login takes two steps. First, fetch a salt (valid 5 minutes) with the tenant + username. Then hash the password with the salt and exchange it for an access token (valid 12 hours). After that, every protected request must carry three auth headers: X-Auth-Tenant, X-Auth-Login, X-Auth-Token.

bash
# 1) Fetch the salt
curl -s -X POST http://localhost:8000/api/v3/auth/token/salt \
  -H 'Content-Type: application/json' \
  -d '{"tenant":"default","name":"dc3"}'
# Example response: {"ok":true,"code":"...","message":"...","data":"a1b2c3d4e5"}

# 2) Hash the password with the salt and exchange it for a token (see the auth docs for the hashing algorithm; PASSWORD_HASH here is an example)
curl -s -X POST http://localhost:8000/api/v3/auth/token/generate \
  -H 'Content-Type: application/json' \
  -d '{"tenant":"default","name":"dc3","salt":"a1b2c3d4e5","password":"<PASSWORD_HASH>"}'
# Example response: {"ok":true,"code":"...","message":"...","data":"<ACCESS_TOKEN>"}
bash
# The CLI wraps fetching the salt, hashing, and exchanging for a token
dc3 auth login --tenant default --username dc3
# Enter the password interactively; the token is saved after login

# Verify
dc3 auth status
dc3 auth token --header   # Print X-Auth-Tenant/X-Auth-Login/X-Auth-Token

What you should see: /api/v3/auth/token/salt returns a non-empty salt; /api/v3/auth/token/generate returns a long token (the <ACCESS_TOKEN> above). On the CLI path, dc3 auth status shows you logged in.

Every Subsequent Request Needs the Auth Headers

To keep the curl examples short, the three headers are extracted into variables below. Set them in your shell first, using the real values from the previous step:

bash
H_TENANT='X-Auth-Tenant: default'
H_LOGIN='X-Auth-Login: dc3'
H_TOKEN='X-Auth-Token: <ACCESS_TOKEN>'   # example

Step 3: Confirm the Virtual Driver Is Registered

When the virtual driver starts with make up-dev, it registers itself with the Manager Center. You need its driverId to create a device, so look it up first.

bash
curl -s -X POST http://localhost:8000/api/v3/manager/driver/list \
  -H "$H_TENANT" -H "$H_LOGIN" -H "$H_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"page":{"current":1,"size":20}}'
bash
dc3 driver list

What you should see: the list contains a driver whose driverName is Virtual Driver (with a space, from dc3.driver.name in application.yml). Its driverCode is the routing identifier VirtualDriver, and the module/service name is dc3-driver-virtual — three different fields. Grab its id, called <DRIVER_ID> below (example: 92010100000000001). The virtual driver is a driver authoring template: it exists for testing and bootstrapping new drivers, and produces data without any real device attached.

Step 4: Add a Profile

A profile describes what a class of devices can do. Here we create a minimal profile to hold the points. Set profileShareFlag to TENANT (shared within the tenant) and enableFlag to ENABLE (enabled).

bash
curl -s -X POST http://localhost:8000/api/v3/manager/profile/add \
  -H "$H_TENANT" -H "$H_LOGIN" -H "$H_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"profileName":"Virtual Thermo Profile","profileShareFlag":"TENANT","enableFlag":"ENABLE"}'
# Example response: {"ok":true,"code":"...","data":"81010100000000001"}
bash
dc3 profile create --name "Virtual Thermo Profile"

What you should see: data holds the new profile ID, called <PROFILE_ID> below (example: 81010100000000001).

profileShareFlag Values

ProfileShareTypeEnum is TENANT / DRIVER / USER — whether the profile is shared within the tenant, the driver, or the user.

Step 5: Add a Point

A point is the data item you collect or write. Whether it's writable is set by the point's own rwFlag. Here we create a READ_WRITE point so we can send write commands to it later. Set pointTypeFlag to FLOAT, attach it to the profile from the previous step, and give it the unit °C.

bash
curl -s -X POST http://localhost:8000/api/v3/manager/point/add \
  -H "$H_TENANT" -H "$H_LOGIN" -H "$H_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
        "pointName":"Temperature",
        "pointTypeFlag":"FLOAT",
        "rwFlag":"READ_WRITE",
        "profileId":"81010100000000001",
        "valueDecimal":2,
        "unit":"°C",
        "enableFlag":"ENABLE"
      }'
# Example response: {"ok":true,"code":"...","data":"82010100000000001"}
bash
dc3 point create --name "Temperature" --profile-id "81010100000000001"

What you should see: the response returns the new point ID, called <POINT_ID> below (example: 82010100000000001).

rwFlag and pointTypeFlag Values

RwTypeEnum is READ_ONLY / WRITE_ONLY / READ_WRITE; a write command against a READ_ONLY point is rejected. PointTypeEnum has 8 values: STRING / BYTE / SHORT / INT / LONG / FLOAT / DOUBLE / BOOLEAN. A point can also carry a conversion (baseValue / multiple) that linearly maps the raw value to an engineering value.

Step 6: Add a Device

A device is a concrete instance bound to one profile and one driver. Use the <DRIVER_ID> and <PROFILE_ID> from above to create it.

bash
curl -s -X POST http://localhost:8000/api/v3/manager/device/add \
  -H "$H_TENANT" -H "$H_LOGIN" -H "$H_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
        "deviceName":"Virtual Thermo Device-01",
        "driverId":"92010100000000001",
        "profileId":"81010100000000001",
        "enableFlag":"ENABLE"
      }'
# Example response: {"ok":true,"code":"...","data":"83010100000000001"}
bash
dc3 device create --name "Virtual Thermo Device-01" \
  --driver-id "92010100000000001" \
  --profile-id "81010100000000001"

What you should see: the response returns the new device ID, called <DEVICE_ID> below (example: 83010100000000001).

Step 7: Configure Point Attributes

At startup, the driver declares which config items (attributes) it has. This step fills in a concrete value for one of those attributes, for this point on this device — a config. This is what actually wires the point into the driver's collection logic. The attributeId comes from an attribute the virtual driver registered (find it in the driver details or the attribute list), and configValue is the value you supply for it.

bash
curl -s -X POST http://localhost:8000/api/v3/manager/point_attribute_config/add \
  -H "$H_TENANT" -H "$H_LOGIN" -H "$H_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
        "attributeId":"91010100000000001",
        "deviceId":"83010100000000001",
        "pointId":"82010100000000001",
        "configValue":"25.0",
        "enableFlag":"ENABLE"
      }'
# Example response: {"ok":true,"code":"...","data":"84010100000000001"}
bash
# The CLI has no standalone attribute-config subcommand yet; use the curl above

What you should see: the response returns the new config ID. Once the config takes effect, the virtual driver starts producing values for that point.

Where attributeId Comes From

attributeId refers to a point attribute (PointAttribute) the virtual driver registered in the Manager Center. Different drivers declare different attributes — this is the "protocol-layer Attribute vs. instance-layer Config" split from the core concepts. The example ID is a placeholder; use the attribute ID actually registered by the virtual driver in your environment.

Step 8: Read Live Point Values

Once values flow, read the latest point values from the Data Center. /point_value/latest filters by deviceId / pointId at the top level, takes pagination in the nested page object (current / size), and returns Page<PointValueVO>.

bash
curl -s -X POST http://localhost:8000/api/v3/data/point_value/latest \
  -H "$H_TENANT" -H "$H_LOGIN" -H "$H_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"deviceId":"83010100000000001","pointId":"82010100000000001","page":{"current":1,"size":10}}'
# Example response (PointValueVO shape):
# {"ok":true,"data":{"records":[
#   {"deviceId":"83010100000000001","pointId":"82010100000000001",
#    "rawValue":"25.0","calValue":"25.0","numValue":25.0,
#    "hasLatestValue":true,"createTime":"2026-06-22T08:30:00","operateTime":"2026-06-22T08:30:00"}
# ],"total":1,"current":1,"size":10}}
bash
dc3 point read 82010100000000001

What you should see: at least one PointValueVO in records, with rawValue (the raw value), calValue (the engineering value, as a string), numValue (the numeric projection, nullable), and the collection time createTime. The value refreshes as the virtual driver keeps running.

Step 9: Issue a Write Command

Finally, send a write command to this writable point. /point_command/write takes deviceId / pointId / value and returns a command ID (commandId) right away — which means "the command was accepted," not "it ran successfully." To get the result, take the command ID and poll the receipt endpoint /point_command_history/get_by_command_id (with that commandId).

bash
# 1) Send the write command and get a commandId immediately
curl -s -X POST http://localhost:8000/api/v3/data/point_command/write \
  -H "$H_TENANT" -H "$H_LOGIN" -H "$H_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"deviceId":"83010100000000001","pointId":"82010100000000001","value":"26.5"}'
# Example response: {"ok":true,"code":"...","data":"cmd_20260622_a1b2c3d4"}

# 2) Poll the receipt with the commandId
curl -s -X GET 'http://localhost:8000/api/v3/data/point_command_history/get_by_command_id?commandId=cmd_20260622_a1b2c3d4' \
  -H "$H_TENANT" -H "$H_LOGIN" -H "$H_TOKEN"
# Example response (PointCommandHistoryVO shape):
# {"ok":true,"data":{
#   "commandId":"cmd_20260622_a1b2c3d4","deviceId":"83010100000000001","pointId":"82010100000000001",
#   "requestValue":"26.5","responseValue":"...","status":"...","finishTime":"2026-06-22T08:31:02"}}
bash
# Send the write
dc3 point write 82010100000000001 --device-id 83010100000000001 --value 26.5
# Poll the receipt
dc3 command history cmd_20260622_a1b2c3d4

What you should see: the write returns a commandId right away; poll the receipt until status reaches a terminal state. That closes the loop — you've run the full bidirectional path of "read value + write command."

Write Command Semantics: Asynchronous, Poll-Required, Failure Does Not Echo the Value

  • Write commands are asynchronous: /point_command/write returns a commandId immediately and does not wait for the device to finish. Poll for the result with that ID at /point_command_history/get_by_command_id.
  • Commands have a TTL: PointCommandDTO.expireAt defaults to now + 10s. A command not executed by then is expired.
  • On failure, the written value is not echoed back: when the receipt shows a failure, it carries no "value that was written" — so don't read "got a commandId" as "the write succeeded."
  • Only points whose rwFlag includes write permission (WRITE_ONLY / READ_WRITE) accept write commands.

Further Reading

  • Device Onboarding — expand this minimal path into the full onboarding flow for a real driver
  • Data and Commands — the full mechanics of collection persistence and read/write commands, plus receipt semantics, across the two planes
  • CLI Guide — the complete dc3 CLI command surface, with scripting and AI-integration usage

Released under the AGPL-3.0 License · 基于 AGPL-3.0 协议发布