Configure Modbus devices

Needs Edge Server 5.4.2 or later.

A Growbud Controller has one RS-485 port. As a Modbus master, the Controller polls the devices on that bus. It also switches the devices that have coils, for example a Waveshare Modbus RTU Relay board. The Controller keeps the configuration and runs it alone. The Edge Server stores the configuration, sends changes over LoRaWAN and publishes the point values.

This guide has two halves. The first half sets up a device in the Growbud app. The second half does the same through the Write API, then writes a point.

Before you start

  • A Controller on your Edge Server, with firmware that reports its Modbus capabilities. The app shows the capabilities under Controller capacity.
  • The device wired to the RS-485 terminals: A to A, B to B, and a common ground.
  • The device's address, baud rate, parity and stop bits. Every device on one bus must use the same line format.

Set the address and line format before the device joins a shared bus. The Waveshare relay boards ship at address 1, 9600 baud, 8N1. Two boards at address 1 answer at the same time, and you cannot read either board. Set the address with the vendor tool on the bench. The Growbud profiles do not change the line format of the board. The vendor documents give two different encodings for that setting. A wrong value puts the board on a line format that no master uses.

How long a change takes

The Edge Server sends configuration changes and point writes to the Controller over LoRaWAN Class C. On the bench:

ActionTime
Send an 800-byte configuration (19 frames of up to 53 bytes)About 70 seconds
Write one point and get the confirmationAbout 20 seconds

The 53-byte frames need Controller firmware 5.4.2 or later.

Plan for these times.

In the Growbud app

The steps are the same in the web app and the desktop app.

1. Open the Controller

  1. Select Control in the sidebar.
  2. Select the Controller in the list.
  3. Open Controller settings.
  4. Select the Fieldbus tab.
The Controller page with Controller settings open on the Fieldbus tab
Control, then the Controller, then Controller settings and Fieldbus.

2. Set the port role

  1. Under RS-485 port, select Modbus Master.
  2. Select Open Modbus setup.

The port has one role at a time.

The RS-485 port card with the four roles and the Open Modbus setup button
The RS-485 port. Modbus Master makes the Controller poll the bus.

3. Read the status

The Modbus setup opens with the last operation at the top. Applied means that the Controller confirmed the configuration. Below it is Controller capacity. It shows how many devices, profiles, reads and points the Controller can hold. It also shows how much of that this configuration uses.

Modbus master setup with an applied operation and the Controller capacity card
Version 1 applied. The Controller confirmed it over LoRaWAN.

4. Edit the configuration

  1. Select Edit configuration. The draft stays in the app until you apply it.
  2. Set Controller role to Master / poll devices.
  3. Set the baud, parity and stop bits under RS-485 bus. They apply to every device on the bus.
The draft editor with the Controller role, the RS-485 bus settings and the capacity rail
The draft. The capacity rail updates as you add devices.

5. Add a device from the library

  1. Select Add sensor to add a device.
  2. Search the library, for example for Waveshare or SenseCAP.

Each library card is a profile. A profile is the list of registers of one device model. An entry is one register in the profile. Each card shows the maker, the model, the category and the number of entries. A card marked Best-effort map comes from the common layout of a part, not from a vendor manual. Check its entries against the device.

The device library filtered to Waveshare, with the 8, D and 16 channel relay boards
The library. One Waveshare 8-channel board is already on this bus.
  1. Select the card.
  2. Give the device a Name.
  3. Set its Address. The note under the address shows the next free address on the bus.
  4. Select how often the Controller reads the device.
  5. Select Add sensor to add the device to the draft.
The Waveshare Modbus RTU Relay details with name, address and read rate
The Waveshare relay. Each relay is a coil that the Controller switches and reads back.

6. Check the device card

Each device in the draft has a card.

  • Profile revision is the profile revision that the Controller will use.
  • Poll device turns the reads on.
  • Readings to poll sets the entries that the Controller reads. Each polled entry becomes a point.
A device card for the Waveshare relay with its profile revision and the readings to poll
The device card. Relay 1 to Relay 8 and the firmware version are the points.

7. Apply and watch the operation

  1. Select Review changes.
  2. Select Confirm and apply.

The Edge Server saves a new version and sends it to the Controller over LoRaWAN. The operation card at the top shows the frames sent, the attempt and the transport. Applied means that the Controller took the new version. An 800-byte configuration is 19 frames of up to 53 bytes and applies in about 70 seconds.

To stop an operation that the Controller has not taken yet, select Cancel queued update.

8. Read the points

Process points lists the points of the configuration that the Controller confirmed. Each point has a stable key, for example ws8.relay_1, and a stable ID. A Read / write or write point accepts a runtime write. A read point does not.

Process points for the Waveshare relay, with Relay 1 to Relay 4 and their stable keys
Process points. The key is what the Write API uses.

Audit trail lists the completed operations, with the delivery evidence and the outcome of each.

The firmware version point and the audit trail with one applied operation
The audit trail. Version 0 to 1, a complete snapshot, applied.

Through the Write API

Every Modbus read is a GET under /sensing/v1/ and accepts any Edge Server token. Every Modbus change is under /control/v1/, needs a token with the control scope, and writes an audit row. See Authentication.

CallWho can make it
Read the configuration, capabilities, profiles, operations and pointsAny token of your organization
Validate, replace, change, deploy, cancel, add a profileA control token of an organization admin
Write a pointAny control token of your organization

A user who is not an organization admin gets 403 permission_denied from the Modbus change routes. A serial outside your organization gives 404.

Put your Controller serial in SERIAL and your Edge Server address in EDGE. The Edge Server uses a self-signed certificate, so the examples use curl -k. Trust the certificate to remove -k.

export TOKEN=<your control token>
export EDGE=https://<your Edge Server address>:8443
export SERIAL=<your Controller serial>
export CTRL=$EDGE/control/v1/controllers/$SERIAL
export SENSE=$EDGE/sensing/v1/controllers/$SERIAL

The steps follow this order: capabilities and profiles, configuration, validate, replace or change, follow the operation, then points and point write.

1. Read the capabilities and the profiles

curl -sk -H "Authorization: Bearer $TOKEN" "$SENSE/modbus/capabilities"
curl -sk -H "Authorization: Bearer $TOKEN" "$SENSE/modbus/profiles"

The capabilities answer is { "capabilities": { ... } }. It says what the Controller firmware can do. It lists the Modbus function codes, the value types and the register orders that the firmware reads. It also gives limits such as max_devices, max_profiles, max_entries_per_profile and max_operations_per_transaction. status is reported, model_default or unknown. It is unknown until the Controller has reported its capabilities.

The profiles answer is { "profiles": [ ... ] }. These are the profiles that your organization can use, the device library included. Without a query, the answer has the current revision of each profile. Add ?profile_id=<id> for one profile, and &revision=<n> for one revision of it.

Find the profile you need. Copy its profile_id and revision, for example for the Waveshare Modbus RTU Relay (8ch).

2. Read the configuration

curl -sk -H "Authorization: Bearer $TOKEN" "$SENSE/modbus/config"
{
  "config": {
    "device_id": "<serial>",
    "etag": "modbus:1:sha256:...",
    "desired_version": 1,
    "device_acked_version": 1,
    "sync_status": "in_sync",
    "config": { "mode": "master", "bus": { "...": "..." }, "devices": [] },
    "capacity": { "...": "..." },
    "last_operation": { "operation_id": "...", "status": "applied", "target_version": 1 }
  }
}

Keep etag and desired_version. Every change needs both. Read etag from the body; the API does not send an ETag header.

A Controller with no configuration answers with the empty default at desired_version 0, not with 404.

sync_statusMeaning
in_syncThe Controller runs desired_version.
draftA saved version is not sent.
queuedAn operation waits to send.
deployingThe Edge Server sends the version.
awaiting_device_commitThe Controller has the frames and has not committed them yet.
blockedThe configuration does not fit the Controller's capabilities.
errorThe last operation failed. Read last_error.

3. Validate a change

curl -sk -X POST "$CTRL/modbus/config/validate" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"base_desired_version": 1, "snapshot": { ... }}'

Send base_desired_version and exactly one of snapshot or operations, the same body as step 4. The answer is 200 with { "validation": { ... } }. Validate saves nothing. An invalid configuration gives 422 invalid_argument with details.validation. A planned bus use above 70 percent gives the warning BUS_UTILIZATION_HIGH.

4. Replace or change the configuration

PUT replaces the whole configuration with a snapshot. PATCH changes one part with operations. Both need two headers:

HeaderRule
If-MatchRequired. The etag from step 2, for example "modbus:1:sha256:...". Without it the answer is 428.
Idempotency-KeyOptional, 8 to 128 characters. Send one to retry safely. The same key and body give the same answer. Without it, the Edge Server makes a key for this call only.
curl -sk -X PUT "$CTRL/modbus/config?wait=30" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'If-Match: "modbus:1:sha256:..."' \
  -H "Idempotency-Key: $(uuidgen)" \
  -d @- <<'EOF'
{
  "base_desired_version": 1,
  "snapshot": {
    "mode": "master",
    "bus": { "slave_address": 1, "baud": 9600, "parity": "none", "stop_bits": 1 },
    "devices": [
      {
        "id": "ws8",
        "name": "Waveshare relay 8ch",
        "address": 1,
        "profile": { "profile_id": "<profile_id from GET profiles>", "revision": 1 },
        "enabled": true,
        "read_entry_ids": [
          "relay_1", "relay_2", "relay_3", "relay_4",
          "relay_5", "relay_6", "relay_7", "relay_8",
          "firmware_version"
        ]
      }
    ]
  }
}
EOF
FieldMeaning
base_desired_versionThe desired_version from step 2.
modemaster: the Controller polls the bus. slave: a building system polls the Controller.
busThe line format of the bus: baud, parity (none, even, odd), stop_bits (1 or 2). slave_address is the Controller's own address in slave mode.
devices[].idYour short name for the device. It becomes the first part of each point key, for example ws8.relay_1.
devices[].profileThe profile, by profile_id and revision.
devices[].enabledtrue turns the reads on.
devices[].read_entry_idsThe entries that the Controller polls. Each polled entry becomes a point.

To change one part, send PATCH $CTRL/modbus/config with the same headers and operations in place of snapshot. Send at most 16 operations, or fewer if the capabilities give a lower max_operations_per_transaction.

{
  "base_desired_version": 2,
  "operations": [
    { "op": "upsert_device", "device": { "id": "ws8", "...": "..." } }
  ]
}
opField
set_modemode
set_busbus
upsert_devicedevice
delete_devicedevice_id

The answer is { "result", "operation", "poll" }:

  • 200 when the operation ended within wait seconds. operation.status tells how it ended.
  • 202 when the operation is still running. Follow poll.
  • 200 with result.content_unchanged: true and no operation when the configuration is the same as before.

result has the new desired_version and etag. Keep them for the next change. wait is 0 to 30 seconds. A configuration transfer takes longer than 30 seconds, so expect 202. A long wait does not make the transfer faster. Follow the operation instead.

5. Follow the operation

curl -sk -H "Authorization: Bearer $TOKEN" "$SENSE/modbus/operations/<operation_id>?wait=30"

The answer is { "operation": { ... } }. It has status, target_version, progress (bytes, pages and frames), retry and cancellation. With wait, the answer comes when the operation ends or the time ends. Call it again until the operation ends.

statusMeaning
queued, dispatching, transferringThe Edge Server sends the frames.
awaiting_device_commitThe Controller has the frames and has not committed them yet.
cancel_requestedA cancel is in progress.
appliedThe Controller runs the new version.
rejectedThe Controller refused the version.
failed, expired, superseded, cancelledThe operation ended without the change.

To list operations, GET $SENSE/modbus/operations. Add view (active, history or all), limit (up to 100), cursor and status. Repeat status for more than one status.

6. Send the configuration again, or cancel

If the Controller missed the change, send the saved version again. target_version must be the current desired_version. Deploy needs If-Match too.

curl -sk -X POST "$CTRL/modbus/deploy?wait=30" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -H 'If-Match: "modbus:2:sha256:..."' -H "Idempotency-Key: $(uuidgen)" \
  -d '{"target_version": 2}'

Deploy makes a new operation and does not change the content. The answer has the same shape as step 4.

To stop an operation that the Controller has not taken yet, send its target_version. Cancel does not need If-Match.

curl -sk -X POST "$CTRL/modbus/operations/<operation_id>/cancel" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"target_version": 2}'

The answer is 200 with { "result": { ... } }. A queued operation ends cancelled, and sync_status goes back to draft. An operation that is already sending gives 422 (CONFIG_ABORT_UNSUPPORTED) or 409 (CONFIG_OPERATION_NOT_CANCELLABLE).

7. Add your own profile

For a device that is not in the library, send PUT $CTRL/modbus/profiles with a profile.

{
  "profile": {
    "name": "My meter",
    "preset_key": null,
    "manufacturer": "Example",
    "model": "M1",
    "entries": [ { "entry_id": "voltage", "...": "..." } ]
  }
}
  • name, preset_key, manufacturer, model and entries are required. preset_key, manufacturer and model are a string or null. A missing field gives 422 with INVALID_PROFILE_FIELD.
  • Leave out profile_id for a new profile.
  • To add a revision, send its profile_id and base_revision. If a newer revision exists, the answer is 409 conflict with MODBUS_PROFILE_REVISION_CONFLICT.

The answer is 200 with { "profile": { "profile_id", "revision", ... } }. The Edge Server never changes a saved revision. A Controller uses it only after you deploy a configuration that refers to it. The API does not retire profiles.

8. Read the points

curl -sk -H "Authorization: Bearer $TOKEN" "$SENSE/modbus/points"
{
  "configVersion": 2,
  "points": [
    {
      "key": "ws8.relay_1",
      "id": "...",
      "device": "ws8",
      "entry": "relay_1",
      "name": "Relay 1",
      "valueType": "boolean",
      "access": "read_write",
      "writable": true
    }
  ]
}

The list follows the configuration that the Controller confirmed. Until then, configVersion is null and points is empty. access is read, write or read_write. The list shows a read point only when the configuration polls it. The list always shows a writable point. The list has no current value.

9. Write a point

curl -sk -X POST "$CTRL/modbus/points/ws8.relay_1/write?wait=30" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"value": true}'
FieldMeaning
valuetrue or false for a coil. A number for a register. A label for a register with named values.
verifyOptional, default true. The Controller reads the register back after the write.

The path takes the point key or its id. The write goes over LoRaWAN and confirms in about 20 seconds, so use wait=30. The answer is 200 when the Controller answered within wait, or 202 with a poll path. command.modbus has verified, deviceStatus and readbackRaw. The read-back is the confirmed state.

deviceStatus is ok when the write succeeded. Any other value is a failure, for example exception, timeout or verify_mismatch. It is null until the Controller answers. The other values are config_mismatch, point_not_found, not_writable, invalid_value, interlock_rejected, busy_retryable, garbled, cancelled, expired, role_changed, config_changed and internal.

The Edge Server pins the write to the configuration version that the Controller confirmed. The write cannot land on a register of a newer profile. Idempotency-Key is optional. The same key and body give the same command.

The Waveshare boards have no communication watchdog. When the bus goes quiet, every relay holds its last state. Do not use a relay alone for a load that must drop out on a fault. Use a contactor for pumps, ballasts and heaters.

Common errors

Errors have the shape { "error": { "code", "message", "reason", "details" } }. reason is the detailed code that the Edge Server got from its Controller functions.

HTTPcodereasonCause and action
403permission_deniedThe token has no control scope, or the user is not an organization admin.
428failed_preconditionPRECONDITION_REQUIREDIf-Match is missing. Send the etag.
400invalid_argumentINVALID_IF_MATCHIf-Match is not one etag from step 2, in double quotes.
412failed_preconditionCONFIG_REVISION_CONFLICTThe configuration changed since you read it. Read it again.
409conflictCONFIG_OPERATION_ACTIVEAnother deployment is active. Follow it, then try again.
409conflictIDEMPOTENCY_KEY_REUSEDThe key was used for a different body. Use a new key.
404not_foundNo point has this key, or the Controller has not confirmed a configuration. Read the points again.
409failed_preconditionMODE_LOCKEDThe current link to the Controller cannot carry this command.
409managed_elsewhereThe Edge Server is on standby. Make the change on the primary.
422invalid_argumentMODBUS_ENTRY_NOT_WRITABLEThe point is read-only.
422invalid_argumentMODBUS_MASTER_MODE_REQUIREDThe Controller is not in master mode.
422invalid_argumentMODBUS_WRITE_VALUE_OUT_OF_RANGE, MODBUS_WRITE_VALUE_TYPE_MISMATCH, MODBUS_ENUM_VALUE_INVALID, MODBUS_WRITE_VALUE_NOT_REPRESENTABLEThe value does not fit the point.
422invalid_argumentMODBUS_CONTROLLER_DEVICE_DISABLEDThe device of the point is not polled. Turn on Poll device.
422invalid_argumentMODBUS_WRITE_CAPABILITY_UNSUPPORTED and other MODBUS_* codesThe Controller firmware does not support this write. Read message.
422invalid_argumentvalidation codesThe configuration does not fit the Controller's capabilities. Read details.validation.
502unavailableThe Controller functions did not answer. A PATCH with more operations than the limit also gives this answer. Check the count before you try again.

Next