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 scoped —
let/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
└──────────────────────┘
- Compilation — BtScript compiler parses
.btssource and generates C# classes implementingIFlow,IFlowTask, orIFlowFunction - Assembly Generation — Flow Service uses Roslyn to compile C# into a .NET assembly
- Storage — Assembly bytes and metadata stored in PostgreSQL artifact registry
- 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 typestrigger— Specifies when the flow executes (on-anymeans "when any listed input changes")let— Binds computed values to lexically scoped variablesemit— 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
- Language Reference — Full syntax guide, built-in functions, and operators
- flowctl CLI Reference — Compile, check, and format
.btsfiles from the command line - Editor Setup — Configure syntax highlighting for
.btsfiles - Flows Overview — Overview of all flow authoring options
- Writing C# Flows — Write flows in C# instead of BtScript
- Compilation & Runtime — Pipeline architecture details
- Persistence & State — State management strategies
- Local Flow Development — Set up your dev environment