> For the complete documentation index, see [llms.txt](https://docs.akenza.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.akenza.io/device-management/rule-engine/logic-blocks/custom-logic/logic-block-scripting.md).

# Logic Block Scripting

How to write the script of a Custom Logic block.

The script of a [Custom Logic](/device-management/rule-engine/logic-blocks/custom-logic.md) block defines the logic that is evaluated each time the rule runs.

{% hint style="info" %}
For general scripting concepts, see [Scripting](/reference/scripting.md).
{% endhint %}

## Script structure

The script defines a `consume` function. It is called with the [event object](#event-object) and emits an action when the condition is met:

```javascript
function consume(event) {
  var temperature = event.inputs.temperature;
  var threshold = event.properties.tempThreshold;

  if (temperature < threshold) {
    emit("action", {
      message: `it is too cold: the temperature is ${temperature}°C`,
    });
  }
}
```

Once the block is saved, it can be selected as a logic block when creating a rule. In the rule:

1. Link each input to an input device or tag.
2. Set the parameters.
3. Add one or more actions. Access the emitted values with `{{result.*}}`, for example `{{result.message}}` for the script above.

To share data between runs of a rule, see [Stateful Operations](/reference/scripting/stateful-operations.md).

## Event object

The `consume` function receives the event object with these properties:

<table><thead><tr><th width="180">Property</th><th width="100">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>inputs</code></td><td>object</td><td>The values of the inputs, by variable name.</td></tr><tr><td><code>properties</code></td><td>object</td><td>The parameters of the block, by variable name.</td></tr><tr><td><code>state</code></td><td>object</td><td>The rule state set by the script.</td></tr><tr><td><code>type</code></td><td>string</td><td>How the rule was invoked: <code>uplink</code> or <code>timer</code>.</td></tr><tr><td><code>dataSources</code></td><td>object</td><td>The data sources of the rule. See <a href="#data-source-properties">Data source properties</a>.</td></tr><tr><td><code>device</code></td><td>object</td><td>The device that triggered the rule. See <a href="#device-properties">Device properties</a>.</td></tr><tr><td><code>timer</code></td><td>object</td><td>Only for timer runs. Contains the <code>meta</code> of an <a href="#emitting-a-timer">emitted timer</a>.</td></tr></tbody></table>

If `type` is `timer`, there is no triggering sample:

* the values in `inputs` are `null`,
* `dataSources` is an empty object,
* `device` is undefined.

### Device properties

Access device properties with the `device.` prefix, for example `device.name`.

<table><thead><tr><th width="180">Property</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>The akenza device ID.</td></tr><tr><td><code>deviceId</code></td><td>The unique device ID, such as the DevEUI.</td></tr><tr><td><code>name</code></td><td>The device name.</td></tr><tr><td><code>description</code></td><td>The device description.</td></tr><tr><td><code>connectivity</code></td><td>The connectivity of the device.</td></tr><tr><td><code>integrationId</code></td><td>The integration ID. Only for LoRaWAN devices.</td></tr><tr><td><code>workspaceId</code></td><td>The ID of the workspace.</td></tr><tr><td><code>dataFlowId</code></td><td>The ID of the data flow.</td></tr></tbody></table>

### Data source properties

Access a data source with `dataSources.X.`, where `X` is the number of the data source in the rule, starting at 1. For example, `dataSources.1.data.temperature`.

<table><thead><tr><th width="180">Property</th><th>Description</th></tr></thead><tbody><tr><td><code>correlationId</code></td><td>The correlation ID. Only set if this data source triggered the rule.</td></tr><tr><td><code>device</code></td><td>The complete device object. See <a href="#device-properties">Device properties</a>.</td></tr><tr><td><code>deviceId</code></td><td>The unique device ID.</td></tr><tr><td><code>akenzaDeviceId</code></td><td>The akenza device ID.</td></tr><tr><td><code>topic</code></td><td>The topic of the sample.</td></tr><tr><td><code>timestamp</code></td><td>The timestamp of the sample.</td></tr><tr><td><code>data.*</code></td><td>The values of the sample.</td></tr><tr><td><code>meta.*</code></td><td>The values of the meta object.</td></tr><tr><td><code>uplinkMeta</code></td><td>The uplink meta object. See <a href="#uplink-meta-properties">Uplink meta properties</a>.</td></tr><tr><td><code>trigger</code></td><td><code>true</code> if this data source triggered the rule.</td></tr><tr><td><code>deviceInput</code></td><td><code>true</code> if the input is a device.</td></tr><tr><td><code>tagInput</code></td><td><code>true</code> if the input is a tag.</td></tr><tr><td><code>tagCombinationInput</code></td><td><code>true</code> if the input is a tag combination.</td></tr></tbody></table>

The sample values (`correlationId`, `topic`, `timestamp`, `data`, `meta`, and `uplinkMeta`) come from the triggering uplink if `trigger` is `true`. Otherwise, they come from the most recent stored sample.

Some properties, such as `correlationId` or `device`, can be `null`, for example when the rule was triggered by a timer or the data source reads the last sample.

### Uplink meta properties

Access uplink meta properties with the `uplinkMeta.` prefix. Timestamps are in ISO 8601 format, and durations are ISO 8601 durations.

<table><thead><tr><th width="220">Property</th><th>Description</th></tr></thead><tbody><tr><td><code>dataReceived</code></td><td>When the data was received.</td></tr><tr><td><code>bytesReceived</code></td><td>The number of bytes in the uplink request.</td></tr><tr><td><code>processingStart</code></td><td>When processing started.</td></tr><tr><td><code>scriptRunUplinkStart</code></td><td>When the script run started.</td></tr><tr><td><code>scriptRunUplinkEnd</code></td><td>When the script run ended.</td></tr><tr><td><code>processingEnd</code></td><td>When processing ended.</td></tr><tr><td><code>outputProduced</code></td><td>When all outputs were produced.</td></tr><tr><td><code>uplinkDuration</code></td><td>The duration of the whole uplink flow.</td></tr><tr><td><code>processingDuration</code></td><td>The duration of processing.</td></tr><tr><td><code>scriptRunningDuration</code></td><td>The duration of the script run.</td></tr></tbody></table>

## Emitting actions

Emit an action to run the rule actions linked to the block:

```javascript
if (currentTemperature > threshold) {
  emit("action", {
    message: `temperature (${currentTemperature} °C) is above threshold (${threshold} °C)`,
  });
}
```

### Re-invoking the rule engine

If the [akenza DB action](/device-management/rule-engine/rule-actions.md#akenza-db-action) has **Invoke Rule engine** enabled, its stored message is evaluated by the rules again. This applies another rule to the output of the block.

In this case, the emitted action needs the same structure as a sample emitted by a device type script: `topic`, `data`, and optionally `timestamp`. See [Emit Function](/reference/scripting.md#emit-function).

```javascript
if (currentTemperature > threshold) {
  emit("action", {
    topic: "alert",
    data: {
      temperature: currentTemperature,
      threshold,
      message: `temperature (${currentTemperature} °C) is above threshold (${threshold} °C)`,
    },
  });
}
```

A `topic` in the emitted action overrides the topic set in the akenza DB rule action.

To also send the message to the outputs of the target device, see [Invoke target data flow](/device-management/rule-engine/rule-actions.md#invoke-target-data-flow).

### Custom payload templates

In a [custom payload template](/reference/message-templating.md), use `{{result.*}}` to access the emitted values. For example, use `{{result.data}}` to store only the data of an action that re-invokes the rule engine. Without it, the whole result is stored as the sample, so `topic`, `timestamp`, `data`, and `meta` all end up in the data object.

## Emitting a timer

Emit a timer to run the rule again at a later time:

```javascript
function consume(event) {
  // run again tomorrow at midnight
  const runAt = new Date();
  runAt.setDate(runAt.getDate() + 1);
  runAt.setHours(0, 0, 0, 0);

  emit("timer", { runAt, meta: { foo: "bar" } });
}
```

<table><thead><tr><th width="120">Parameter</th><th width="120">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>runAt</code></td><td>Yes</td><td>A date at least 15 seconds in the future.</td></tr><tr><td><code>meta</code></td><td>No</td><td>An object with any values.</td></tr></tbody></table>

When the timer runs the rule, `meta` is available as `event.timer.meta`. Use it to pass information to the next run, or to tell several timers apart.

## Event examples

The `state` in these examples is set by the script. See [Stateful Operations](/reference/scripting/stateful-operations.md).

<details>

<summary>Uplink event</summary>

A decoded uplink that triggers a custom logic block:

```json
{
  "inputs": {
    "occupied": false
  },
  "state": {
    "devices": {
      "00000000000000ff": {
        "lastOccupiedTimestamp": 1716553293784,
        "currentlyOccupied": false,
        "lastMessageTimestamp": 1716553385376,
        "sent": true
      }
    }
  },
  "type": "uplink",
  "dataSources": {
    "1": {
      "correlationId": "77777777-7777-7777-7777-77777777",
      "device": {
        "connectivity": "LORA",
        "customFields": {
          "FloorNumber": 1,
          "SiteId": "ZURICH",
          "IsWarmDeskEnabled": "true",
          "SpaceId": "1OGR35"
        },
        "name": "Meeting Room R35",
        "description": "",
        "id": "00000000000000ff",
        "deviceId": "1234567891012131",
        "workspaceId": "2900000000000000",
        "tags": [
          "occupancy",
          "climate"
        ]
      },
      "deviceId": "1234567891012131",
      "akenzaDeviceId": "00000000000000ff",
      "topic": "occupancy",
      "timestamp": "2024-05-24T12:38:06.122Z",
      "data": {
        "occupied": false,
        "motionCount": 0
      },
      "trigger": true,
      "deviceInput": false,
      "tagInput": true,
      "tagCombinationInput": false,
      "triggerOnUplink": true,
      "lastSample": false
    }
  },
  "device": {
    "connectivity": "LORA",
    "customFields": {
      "FloorNumber": 1,
      "SiteId": "ZURICH",
      "SpaceId": "1OGR35"
    },
    "name": "Meeting Room R35",
    "description": "",
    "id": "00000000000000ff",
    "deviceId": "1234567891012131",
    "workspaceId": "2900000000000000",
    "tags": [
      "occupancy",
      "climate"
    ]
  },
  "numberOfInvocations": 0,
  "properties": {}
}
```

</details>

<details>

<summary>Timer event</summary>

A run triggered by a timer that the script emitted with a `meta` object:

```json
{
  "inputs": {
    "occupied": null
  },
  "state": {
    "devices": {
      "00000000000fffff": {
        "lastOccupiedTimestamp": 1716553293784,
        "currentlyOccupied": false,
        "lastMessageTimestamp": 1716560583110,
        "sent": false
      }
    }
  },
  "type": "timer",
  "dataSources": {},
  "numberOfInvocations": 0,
  "properties": {},
  "timer": {
    "meta": {
      "id": "00000000000fffff",
      "alarmId": 1,
      "value": 1500
    }
  }
}
```

</details>

<details>

<summary>Timer event with last sample</summary>

A run triggered by a timer where the data source reads the last sample of its device:

```json
{
  "inputs": {
    "temperature": 23.84
  },
  "state": {},
  "type": "timer",
  "dataSources": {
    "1": {
      "correlationId": "77777777-7777-7777-7777-777777777777",
      "device": {
        "connectivity": "LORA",
        "customFields": {},
        "name": "Watteco Senso",
        "description": null,
        "id": "1998877667788991",
        "deviceId": "000000000000ffff",
        "workspaceId": "2929292929292929",
        "tags": []
      },
      "deviceId": "000000000000ffff",
      "akenzaDeviceId": "1998877667788991",
      "topic": "default",
      "timestamp": "2024-05-24T13:20:14.869Z",
      "data": {
        "temperature": 23.84,
        "humidity": 51.3,
        "voc": 553,
        "co2": 518
      },
      "meta": null,
      "uplinkMeta": {
        "dataReceived": "2024-05-24T13:20:14.150224453Z",
        "bytesReceived": 952,
        "processingStart": "2024-05-24T13:20:14.220414139Z",
        "scriptRunUplinkStart": null,
        "scriptRunUplinkEnd": null,
        "processingEnd": "2024-05-24T13:20:14.259641209Z",
        "outputProduced": null,
        "ruleResolutionStart": "2024-05-24T13:20:14.805071863Z",
        "ruleResolutionEnd": null,
        "ruleExecutionStart": null,
        "ruleExecutionEnd": null,
        "uplinkDuration": "PT0S",
        "processingDuration": "PT0.03922707S",
        "ruleResolutionDuration": "PT0S",
        "ruleExecutionDuration": "PT0S",
        "scriptRunningDuration": "PT0S"
      },
      "trigger": false,
      "deviceInput": true,
      "tagInput": false,
      "tagCombinationInput": false,
      "triggerOnUplink": false,
      "lastSample": true
    }
  },
  "numberOfInvocations": 0,
  "properties": {}
}
```

</details>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.akenza.io/device-management/rule-engine/logic-blocks/custom-logic/logic-block-scripting.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
