Skip to main content

BtScript Overview

BtScript is a domain-specific language (DSL) based on Scheme syntax, designed for writing reactive dataflow pipelines. It compiles to C# code and runs as .NET assemblies within FlowGrains.

  • Lexically scopedlet/let* provide lexical scoping within blocks
  • Reactive by design — Flows execute when inputs change (event-driven)
  • Compiled to .NET — Generates C# source code, then compiles to IL via Roslyn
  • Distributed runtime — Runs in Orleans grains for fault tolerance and scalability
  • File extension.bts

BtScript is not a general-purpose language. It's optimized for expressing data transformations, aggregations, and event-driven logic in the context of IoT telemetry and asset management.

Compilation Pipeline

BtScript follows a multi-stage compilation pipeline:

┌──────────────┐
│ .bts source │ Developer writes BtScript code
└──────┬───────┘

v
┌──────────────────────┐
│ BtScript Compiler │ Parses .bts, generates C# code
│ (btscript) │
└──────┬───────────────┘

v
┌──────────────────────┐
│ Roslyn Compilation │ Compiles C# to .NET assembly
│ (flow service) │
└──────┬───────────────┘

v
┌──────────────────────┐
│ Artifact Store │ Stores assembly with metadata
└──────┬───────────────┘

v
┌──────────────────────┐
│ FlowGrain Runtime │ Orleans grain loads assembly,
│ │ executes flow on input events
└──────────────────────┘
  1. Compilation — BtScript compiler parses .bts source and generates C# classes implementing IFlow, IFlowTask, or IFlowFunction
  2. Assembly Generation — Flow Service uses Roslyn to compile C# into a .NET assembly
  3. Storage — Assembly bytes and metadata stored in PostgreSQL artifact registry
  4. Runtime — FlowGrain loads assembly, instantiates flow, and executes on telemetry events

A Simple Example

A BtScript flow that converts Celsius to Fahrenheit and emits the result:

(flow id: temp-conversion
version: "1.0.0"
description: "Convert Celsius temperature to Fahrenheit"

;; Bind inputs to telemetry keys
(inputs
(celsius type: double signal: "sensor.temperature"))

;; Execute flow whenever celsius input changes
(trigger on-any: celsius)

;; Compute Fahrenheit and emit result
(let ((fahrenheit (+ (* celsius 1.8) 32)))
(emit fahrenheit-output channel: event value: fahrenheit)))

Breakdown:

  • flow — Declares this artifact as a Flow with metadata (id, version, description)
  • inputs — Maps BtScript variable names to telemetry signals with types
  • trigger — Specifies when the flow executes (on-any means "when any listed input changes")
  • let — Binds computed values to lexically scoped variables
  • emit — Publishes a value to a named output with channel routing

When deployed and bound to an asset, this flow automatically executes whenever the sensor.temperature telemetry key receives a new value.

Important: A flow will not execute until all declared inputs (both active and passive) have received at least one value.

Output and Emit

Flows have two mechanisms for producing values:

Output (Return Value)

Use (output expr) to return an explicit value from the flow:

(flow id: temp-conversion
version: "1.0.0"
(inputs (celsius type: double signal: "sensor.temperature"))
(trigger on-any: celsius)
(let ((f (+ (* celsius 1.8) 32)))
(output f)))

Emit (Side Effects)

Use (emit ...) to publish values to named outputs with channel routing:

;; Emit to a specific channel
(emit critical-temp channel: alarm value: temp)
(emit high-temp channel: notification value: temp)
(emit temp-normal channel: event value: temp)

Available channels: event, alarm, notification. A flow can use both — output for the return value and emit for side effects.

Stateful Computations

BtScript provides built-in rolling aggregation functions for time-windowed state:

(flow id: temp-monitor
version: "1.0.0"
(inputs (temp type: double signal: "sensor.temperature"))
(trigger on-any: temp)

(rolling-avg window: PT5M input: temp as: avg)
(rolling-max window: PT5M input: temp as: hi)
(emit average-temp channel: event value: avg)
(emit max-temp channel: event value: hi))

Rolling aggregations (rolling-avg, rolling-min, rolling-max, rolling-sum) maintain windowed state that is automatically persisted and survives restarts. Key-value state (GetValue/SetValue) is fully available in C# flows but not yet exposed in BtScript syntax.

Error Handling

BtScript does not have try-catch. Errors are handled at the runtime level — uncaught exceptions halt flow execution and are logged by the FlowGrain.

Tasks and Functions

Task (Stateful)

Tasks are reusable computation units with their own state, invoked by flows:

(task id: rolling-avg
version: "1.0.0"
inputs: ((data double))
output: double
data)

Function (Stateless)

Functions are pure, stateless computations:

(function id: clamp
version: "1.0.0"
inputs: ((x double) (min-val double) (max-val double))
output: double
(cond
((< x min-val) min-val)
((> x max-val) max-val)
(else x)))

Next Steps