> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Callable ACTIONs

> Reuse LoT logic across ACTIONs with callable utilities—shared calculations, validations, and on-demand scripts invoked via CALL ACTION.

## Overview

Most ACTIONs start automatically—on a schedule, when an MQTT message arrives, or at broker startup. A **callable ACTION** has no trigger. It runs only when another ACTION invokes it with `CALL ACTION`.

Callable ACTIONs let you extract logic into named, reusable blocks: unit conversions, threshold checks, aggregations, or short on-demand scripts (refresh a cache, recalculate totals) that run at a specific point in a workflow—not on their own timer or topic.

<Tip>
  **Think of callable ACTIONs as functions inside the broker.** Triggered ACTIONs decide *when* something happens; callables hold *how* to compute, validate, or transform—and run only when called.
</Tip>

***

## When to Use Callable ACTIONs

Before splitting logic out, understand where callables fit:

| Use Callable ACTIONs For                                | Use Triggered ACTIONs For                                  |
| ------------------------------------------------------- | ---------------------------------------------------------- |
| Shared calculations used in multiple places             | Reacting to MQTT messages (`ON TOPIC`, `ON CHANGE`)        |
| Validations and transformations with typed inputs       | Scheduled polling and reports (`ON EVERY`, `ON TIMESTAMP`) |
| On-demand steps inside a workflow (cache refresh, prep) | One-time bootstrap at startup (`ON START`)                 |
| Keeping main ACTION bodies short and readable           | Logic that must run on its own when an event occurs        |

<Info>
  If an ACTION must run by itself when data arrives or on a clock, give it a trigger. Use a callable when the logic is shared or should run only as part of another ACTION's flow.
</Info>

***

## Defining a Callable ACTION

A callable is a normal `DEFINE ACTION` **without** an `ON …` clause. Optionally declare `INPUT` parameters and `RETURN` one or more `OUTPUT` values.

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ACTION <Name>
[INPUT <param> AS <TYPE> ...]
DO
    -- logic
[RETURN
    OUTPUT <variable> ...]
```

### Supported INPUT Types

| Type     | Typical use                    |
| -------- | ------------------------------ |
| `STRING` | IDs, labels, status text       |
| `INT`    | Counts, discrete setpoints     |
| `DOUBLE` | Sensor readings, analog values |
| `BOOL`   | Flags and enable/disable state |

### Parameterless Callable

Use when the block has no inputs—common for on-demand scripts that read from topics or update cached state:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ACTION RefreshDashboardCache
DO
    KEEP TOPIC "cache/dashboard/summary" WITH (GET TOPIC "data/live/summary")
    PUBLISH TOPIC "cache/dashboard/updated" WITH TIMESTAMP "UTC"
```

### Callable with Inputs and Return Value

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ACTION ConvertCelsiusToFahrenheit
INPUT celsius AS DOUBLE
DO
    SET "fahrenheit" WITH ({celsius} * 9 / 5 + 32)
RETURN
    OUTPUT fahrenheit
```

***

## CALL ACTION Syntax

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
CALL ACTION <Name>
    WITH <param> = <expression>, ...
    RETURN <var1>[, <var2>, ...]
```

* `WITH` argument names must match the callee's `INPUT` names.
* `RETURN` variables are local to the caller and receive `OUTPUT` values in declaration order.

## Parameter Passing

Pass one or more values with `WITH`. Each name must match an `INPUT` on the callable:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
CALL ACTION CalculateAverage
    WITH value1 = {temp1}, value2 = {temp2}
    RETURN result
```

Define the callable with a matching `INPUT` for each parameter:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ACTION CalculateAverage
INPUT value1 AS DOUBLE
INPUT value2 AS DOUBLE
DO
    SET "avg" WITH (({value1} + {value2}) / 2)
RETURN
    OUTPUT avg
```

## Return Value Handling

The `RETURN` clause on the caller captures values from the callable's `OUTPUT` declarations.

### Single Return Value

Declare one `OUTPUT` on the callable and one variable on the caller:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ACTION ConvertCelsiusToFahrenheit
INPUT celsius AS DOUBLE
DO
    SET "fahrenheit" WITH ({celsius} * 9 / 5 + 32)
RETURN
    OUTPUT fahrenheit
```

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
CALL ACTION ConvertCelsiusToFahrenheit
    WITH celsius = {temp_c}
    RETURN temp_f
```

### Multiple Return Values

Declare one `OUTPUT` per value. On the caller, list return variables in the **same order** as the `OUTPUT` lines:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ACTION AnalyzeValue
INPUT value AS DOUBLE
INPUT threshold AS DOUBLE
DO
    SET "is_above" WITH ({value} > {threshold})
    SET "difference" WITH ({value} - {threshold})
    SET "percentage" WITH ({value} / {threshold} * 100)
RETURN
    OUTPUT is_above
    OUTPUT difference
    OUTPUT percentage
```

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
CALL ACTION AnalyzeValue
    WITH value = {reading}, threshold = {limit}
    RETURN above, diff, pct
```

The first `OUTPUT` (`is_above`) maps to `above`, the second to `diff`, and the third to `pct`.

***

## Usage Examples

<Tabs>
  <Tab title="Reusable Logic">
    Call a shared conversion from a topic-triggered ACTION instead of repeating the formula inline:

    ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
    DEFINE ACTION ProcessTemperatureReading
    ON TOPIC "sensors/+/celsius" DO
        SET "sensor_id" WITH TOPIC POSITION 2
        SET "temp_c" WITH PAYLOAD AS DOUBLE

        CALL ACTION ConvertCelsiusToFahrenheit
            WITH celsius = {temp_c}
            RETURN temp_f

        PUBLISH TOPIC "sensors/" + {sensor_id} + "/fahrenheit" WITH {temp_f}
    ```
  </Tab>

  <Tab title="On-Demand Scripts">
    A parameterless callable runs only when a triggered ACTION calls it—not on its own schedule or topic:

    ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
    DEFINE ACTION HourlyDashboardReport
    ON EVERY 1 HOUR DO
        CALL ACTION RefreshDashboardCache

        SET "summary" WITH (GET TOPIC "cache/dashboard/summary")
        PUBLISH TOPIC "reports/dashboard/hourly" WITH {summary}
    ```
  </Tab>

  <Tab title="Composing Callables">
    Chain callables in a triggered ACTION so the main body stays focused on orchestration:

    ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
    DEFINE ACTION ProcessTankLevel
    ON TOPIC "tanks/+/level" DO
        SET "tank_id" WITH TOPIC POSITION 2
        SET "level" WITH PAYLOAD AS DOUBLE
        SET "capacity" WITH (GET TOPIC "config/" + {tank_id} + "/capacity" AS DOUBLE)

        CALL ACTION CalculatePercentage
            WITH value = {level}, total = {capacity}
            RETURN fill_pct

        CALL ACTION AnalyzeReading
            WITH current = {level}, threshold = ({capacity} * 0.9)
            RETURN above, diff, pct_of_max

        PUBLISH TOPIC "processed/" + {tank_id} + "/fill_percent" WITH {fill_pct}
        PUBLISH TOPIC "processed/" + {tank_id} + "/over_threshold" WITH {above}
    ```
  </Tab>
</Tabs>

***

## Best Practices

<AccordionGroup>
  <Accordion title="One Callable, One Job" icon="layer-group">
    Each callable should do one clear thing—convert units, validate a range, compute an average. If a block grows large or handles unrelated steps, split it into separate callables and compose them with `CALL ACTION`.
  </Accordion>

  <Accordion title="Extract Before the Third Copy" icon="copy">
    The moment the same expression appears in two ACTIONs, consider a callable. Shared rules (thresholds, conversions, KPI formulas) belong in one place so fixes propagate to every caller on deploy.
  </Accordion>

  <Accordion title="Name for Intent" icon="tag">
    Use PascalCase, function-like names: `ConvertCelsiusToFahrenheit`, `CalculateAverage`, `ValidateReading`. See [Naming conventions](/latest/lot-language/usage-patterns/naming-conventions).
  </Accordion>

  <Accordion title="Keep Triggered ACTIONs Thin" icon="list-check">
    Triggered ACTIONs should read as workflows: read data, call utilities, publish results. Push calculations and validations into callables so topic and schedule handlers stay easy to scan and maintain.
  </Accordion>
</AccordionGroup>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Operations Reference" icon="gear" href="./operations">
    Review all available LoT operations.
  </Card>

  <Card title="ACTION Triggers" icon="clock" href="./events">
    Time-based, topic-based, and initialization triggers.
  </Card>
</CardGroup>
