Bugitrun Logo

ABOUT

BIR platform

Are you a student, teacher, or developer in automation and control? Don’t want to waste time on complex platforms and pay for systems that, in practice, never really pay off? Imagine a small, affordable board that doesn’t look special at first glance, but quietly prepares everything essential – a ready-to-use environment for your project, so you can start building right away.

Bugitrun is a platform designed for both beginners and advanced users in automation. It offers an easy, intuitive, and enjoyable way to bring projects to life. It is built on an open and endlessly expandable architecture, modularity, automatic recognition of connected modules, and cooperation of multiple units within an internal network.

At the core of Bugitrun is the BIR module.
Each BIR includes an integrated test set of inputs and outputs for quick familiarization. It measures temperatures, switches outputs, drives servos, and keeps track of the system state. You monitor everything through a local or cloud dashboard in your browser: you see live values, can intervene manually, adjust settings, and write automation logic in a simple BASIC-like language – while the program itself runs directly inside the BIR.

When that’s not enough, BIR grows with your project.
Its main strength is a 3D spatially oriented bus for connecting external modules – the ecosystem. These modules are open source, and the community can expand the system practically without limits with hundreds of variants for different use cases: relay boards, input blocks, special sensors, and more. Each module announces itself when connected, gets its own objects and tile color, and is ready to use instantly. You don’t rewrite firmware – you just add hardware and use the new objects directly in your logic.

Multiple BIR modules can cooperate over the network using BIRNET.
A BIR in the boiler room can share data with another in the greenhouse, or a “server” BIR can collect values from several rooms and make global decisions. Technically it’s a simple small HTTP API, but from the user’s point of view the system behaves like a single distributed whole, where individual BIRs can see selected values from the others.

Everything is tied together by a simple BASIC-like language running on every BIR.
Your program reads sensor values, sets outputs, calls BIRNET functions, and keeps running even if you close the dashboard or the internet goes down. Just a few readable lines are enough to turn a set of sensors and modules into a system that runs on its own – exactly the way you designed it.

Manual

Initialization is used to configure the BIR module’s Wi-Fi connection, Auth Key and time zone. Without a completed initialization, the module does not enter normal operating mode.

FIRST USE

If the BIR module starts without stored configuration, it automatically enters initialization mode:

  • a temporary Wi-Fi network BUGITRUN_SETUP is created,
  • the password for this network is adminadmin.
BUGITRUN_SETUP Wi-Fi network

The initialization procedure is as follows:

  1. Connect a phone, tablet or computer to the Wi-Fi network BUGITRUN_SETUP (password adminadmin).
  2. Open a web browser and go to http://192.168.4.1.
  3. In the configuration form, fill in:
    • SSID – the name of the target Wi-Fi network,
    • Password – the password of this Wi-Fi network,
    • Auth Key – access key for the cloud dashboard (minimum length 8 characters),
    • Time Zone – select from the list or enter a custom POSIX string.
  4. Submit the form using the Save button.

The module stores the provided data, connects to the configured Wi-Fi network and attempts to complete the initialization with the cloud backend.

Bugitrun initialization form

STATUS INDICATION (LED)

After the initialization attempt, the result is indicated using the status LED:

  • LED permanently on – initialization completed successfully.
  • LED blinking – initialization failed (Wi-Fi / connectivity / server error).

If initialization fails (the LED is blinking fast), the BIR module does not restart automatically and must be restarted by briefly disconnecting and reconnecting the power supply. Then try to start the initialization again by holding the button for 10 seconds.

Bugitrun initialization finished screen

REPEATED INITIALIZATION

Initialization can be repeated at any time, for example when changing Wi-Fi parameters or the Auth Key, or when handing the module over to a new user. To restart initialization, press and hold the setup button on the BIR module for approximately 10 seconds, until the status LED turns on. The device will erase its stored configuration, reboot, create the BUGITRUN_SETUP Wi-Fi network again and expose the initialization page at http://192.168.4.1, so the initial initialization procedure can be performed once more.

Cloud dashboard

COMMON FEATURES OF BOTH DASHBOARDS

Both the Local and Cloud Dashboards share a common set of core features to ensure a consistent experience.

TILE DISPLAY

Both dashboards display sensors and outputs as tiles, each identified by:

  • a three-letter ID,
  • a user-defined name,
  • a float value.

System tiles such as TIM, DTE, DAY, OUT, INP, TMP, VAL use predefined colors. For external modules, the dashboard generates a unique tile color based on the module’s three-letter ID.

Internally, each tile corresponds to an object with a three-letter prefix and a numeric index. The core BIR module provides built-in objects OUT_0OUT_2 (5 V outputs), INP_3 (digital input), SRV_4 and SRV_5 (servo outputs 0–180), followed by optional DS18B20 sensors TMP_6… and channels of external modules such as RLY_x or ADC_x. Numbering is always continuous and any remaining indices are filled with generic VAL_x objects. The total range of tiles is fixed to either 0–44 (no external modules present) or 0–76 (at least one external module detected).

RENAMING OBJECTS

You can rename any object on both dashboards by clicking its name. When choosing a name, it must be unique, must not clash with BASIC keywords, be at most 12 characters long, and may contain only letters, digits, the underscore (_) and the hyphen (-). The user-defined name can then be used directly as an identifier in BASIC code, so you don’t have to reference objects only by their three-letter IDs.

EDITING OUTPUTS

For outputs (e.g. relays), you can click on the value and enter a new float value, which is then sent to the device.

BASIC EDITOR

Both dashboards provide a BASIC-like editor where you write your program. Once uploaded, the program runs directly on the BIR module and continues operating even when the device is offline.

IMPORT / EXPORT

Both dashboards allow you to:

  • export a project to a file,
  • import a project from a file.

This makes it easy to move projects between the Local and Cloud Dashboards or between different BIR modules.

ERASE BUTTON

Each dashboard includes an Erase button with two functions:

  • clear local storage data in your browser,
  • erase the BASIC program from the BIR module and restart the device.

CLOUD DASHBOARD

The Cloud Dashboard runs on the Bugitrun server and is accessed securely over HTTPS using your private access key. It provides a complete remote interface for monitoring and controlling the BIR module from anywhere.

OBJECT GRID

After logging in, you are presented with a live grid of tiles. Each tile represents a single object and updates automatically as new data arrives from the module. Tile colors indicate the object’s module type.

BASIC EDITOR AND PROJECT MANAGEMENT

Below the grid is the BASIC editor – a clean editing area for writing and modifying your program. Under the editor, the following project-management controls are available:

  • load a project,
  • save a project,
  • rename a project,
  • delete a project,
  • run a static code check,
  • upload the program to the BIR module.

Uploaded programs start immediately and continue running even when the device is offline. All Cloud Dashboard projects are stored directly on the server, allowing access from any location.

CORNER MENU

The upper-right corner menu provides these actions:

  • switch to the Local Dashboard,
  • show or hide the graph panel,
  • export a project,
  • import a project,
  • reset the dashboard,
  • update firmware,
  • change the visual skin.

SWITCH TO LOCAL DASHBOARD

Switches to the Local Dashboard hosted directly on the BIR module. This option is available only when your browser is on the same local network as the device.

GRAPH PANEL

Graph

Shows or hides the graph panel. The graph runs entirely inside the browser and stores all data locally in the browser’s memory. Closing or refreshing the page clears the graph. This design provides high performance at scale without any server-side storage.

The graph panel allows you to:

  • assign up to 10 sensors to 10 graph slots,
  • choose left or right Y-axis per sensor,
  • adjust the visible time range,
  • set minimum and maximum value limits,
  • pause drawing,
  • reset the graph,
  • export collected data as a CSV file.

EXPORT AND IMPORT

Export and import allow you to share or transfer projects as files directly between devices. You can move a project between multiple BIR modules or between the Cloud and Local Dashboard simply by exporting it to a file and importing it where you need it.

RESET DASHBOARD

Clears the dashboard state and prepares it for fresh synchronization with the device.

FIRMWARE UPDATE

Performs a secure remote firmware update of the BIR module. The update is protected by a cryptographic digital signature (ECDSA). Updates are applied remotely and do not require physical access to the device.

SKIN SELECTION

Cycles through the available visual skins. Skins rotate in sequence, and additional designs may be added over time.

ONE-SESSION LOCK

The Cloud Dashboard includes a global one-session lock.

  • Only one active Cloud Dashboard session is allowed across the entire internet for a given device.
  • If a second browser or device tries to open the Cloud Dashboard simultaneously, it is blocked to prevent conflicting remote operations.
  • A visible notice appears, and the user may take over the session via a dedicated button.
  • When the takeover is confirmed, the previous session is safely terminated and control is transferred to the new browser.

This mechanism prevents race conditions and ensures that commands sent from the cloud remain consistent and secure.

TYPICAL USAGE

The Cloud Dashboard is ideal for remotely supervising and controlling the BIR module – monitoring sensors, managing programs, performing secure firmware updates, sharing projects between devices, or analyzing data over time. In cloud mode, the dashboard shows updated values from the BIR module roughly every 2 seconds, while the Local Dashboard uses a shorter 0.5-second refresh interval for faster, more immediate feedback when you are on the same network.


LOCAL DASHBOARD

The Local Dashboard runs directly on the BIR module at its local IP address within your network. For reliable access, you are encouraged to reserve this IP address in your Wi-Fi router. There is no cloud layer involved, which means:

  • responses are instant,
  • the interface remains fully functional even without an internet connection.

OBJECT GRID

The main area displays a live grid of tiles:

  • Each tile represents a sensor, output, or time-related object.
  • Values update immediately as the device processes new data.

BASIC EDITOR

Below the grid, the Local Dashboard provides a BASIC code editor for writing and modifying programs. The editor includes:

  • Static code check – same as on the Cloud Dashboard: validates your program before upload.
  • Upload CODE – same as on the Cloud Dashboard: sends the prepared program to the device in its compact internal format for execution.
  • Automatic browser storage – your work is continually saved locally in the browser, preventing data loss on reload.

In addition, the Local Dashboard supports system-level commands directly inside the BASIC editor:

  • Syntax: SYSTEM(COMMAND)
  • Behavior: When the program is uploaded, any SYSTEM() instruction is executed immediately and directly on the BIR module.
  • Scope: This capability is local-only for safety reasons and enables powerful maintenance actions, such as:
    • emergency firmware recovery,
    • resetting internal components,
    • forcing a critical system update.

Command description:

  • SYSTEM(ENABLE_INSECURE_OTA_ONCE) – enables a one-time emergency OTA update without certificate validation.
    • Recommended when secure TLS/certificate validation is broken, for example after a Cloudflare certificate change.
    • Updates the BIR module core firmware only. It does not update the Local Dashboard. To update both the core and the Local Dashboard, perform a standard OTA update from the Cloud Dashboard.

Other SYSTEM() commands may be used in the same way.

QUICK-ACCESS MENU

A small floating menu provides essential local tools:

  • Export project – same as on the Cloud Dashboard: saves the current project (BASIC code + object names) as a JSON file.
  • Import project – same as on the Cloud Dashboard: loads a previously exported JSON project file.
  • Reset Dashboard & Restart – similar to Reset Dashboard on the Cloud Dashboard, but can additionally erase the BASIC program on the device and reboot the module.

ONE-SESSION LOCK

The Local Dashboard enforces a single active session only within the same browser environment.

  • The lock applies between tabs of the same browser.
  • Technically, the module can still be accessed from:
    • another device on the LAN,
    • another browser on the same device,
    • a different platform entirely.

Although this is allowed, it is strongly discouraged to run the Local Dashboard on multiple devices or browsers simultaneously, because it can:

  • interfere with the BIR module’s local communication timing,
  • cause rapid conflicting commands,
  • overload internal channels,
  • break the expected network flow.

For stable operation, the Local Dashboard should be used from a single browser instance at a time. An in-browser overlay prevents parallel sessions within the same browser, but it does not block external devices — therefore the responsibility is on the user to avoid multiple local controllers.

TYPICAL USAGE

The Local Dashboard is ideal for configuration, testing, development, and emergency maintenance, especially when you need fast, low-latency feedback directly from the device. With its 0.5-second refresh interval (compared to 2 seconds on the Cloud Dashboard), it gives you a much faster visual response when your BASIC program changes values or states. The BASIC program itself always runs directly on the BIR module and is not affected by the dashboard refresh interval. The BASIC interpreter runs offline on the BIR module with a minimum step interval of 10 ms for internal system I/O and 100 ms for external modules.

BIR module

The BIR module is built around an ESP32-WROOM-N4 with a dual-core CPU. It exposes a fixed set of built-in hardware channels that are always present and directly accessible from BASIC. All values are stored internally as floating-point numbers, even if they represent digital states.

Power

The BIR module is powered via a USB-C connector. This connector is used for power only and does not provide a data interface. The 5 V rail is also distributed to the external module bus to supply connected expansion modules. Always choose a USB-C power adapter with respect to the connected external modules and their maximum current consumption.

Built-in outputs and inputs

  • OUT_0, OUT_1, OUT_2 – three built-in 5 V outputs.
    Each output is protected by a diode and can drive up to approximately 1 A. Inductive loads (relays, small coils, etc.) can be connected directly within this limit.
  • INP_3 – one built-in digital input.
    The input is galvanically isolated via an optocoupler and accepts voltages in the range 0–30 V. On the dashboards and in BASIC it is represented as a numeric value (typically 0 or 1).
  • SRV_4, SRV_5 – two built-in servo outputs.
    They are designed for standard 5 V RC servos. The expected value range is 0–180 (degrees); values outside this range are internally clamped by the firmware.

Temperature sensors (DS18B20)

  • Connected DS18B20 sensors are mapped to IDs TMP_6TMP_15 (up to 10 sensors in total).
  • If more than one DS18B20 is present, they are automatically ordered by their internal serial number. This means that after each restart the same physical sensor will appear under the same TMP_x ID.
  • If no DS18B20 sensors are connected, no TMP_x objects are created.

System time and date objects

  • TIM_SYS – current time of day (same numeric representation as TIME).
  • DTE_SYS – current date (same numeric representation as DATE).
  • DAY_SYS – current day of week (same numeric representation as DAY).

These objects are updated automatically by the BIR firmware and are read-only from BASIC.

Generic value slots – VAL_x

All remaining IDs up to the current maximum tile index are exposed as VAL_x objects. They are pure numeric slots stored in the BIR module and are not tied to any specific hardware. They are intended as general-purpose variables that survive restarts and can be used in any BASIC program.

If no external modules are connected, the dashboards show tiles from index 0 up to 44 (OUT / INP / SRV / TMP and then a block of VAL_x objects). If at least one external module is detected, the available tile range is extended to 0–76. The free space between the last hardware ID and the maximum index is always filled with VAL_x.

Base platform characteristics

The built-in BIR hardware (OUT / INP / SRV / TMP / VAL) is primarily meant for learning, prototyping and smaller projects. Because these channels are connected directly to ESP32 pins, the internal response is very fast and latency is minimal.

Communication with dashboards

  • Local Dashboard – communicates with the BIR module over plain HTTP (no encryption). Intended for local network use.
  • Cloud Dashboard – communicates over HTTPS using ECDSA and HMAC for authentication and integrity protection.
BIR module
BIR Network

BIRNET allows multiple BIR modules to communicate with each other directly over the local network, without using the cloud backend. Typical use cases include sharing sensor values between rooms, synchronizing outputs across devices, or offloading part of the logic to a dedicated “server” BIR.

BIRNET is implemented as a small HTTP-based API between BIR modules. Each BIR exposes simple LAN endpoints and the BASIC interpreter provides high-level commands that hide the HTTP details and handle timeouts internally.

SINGLE-VALUE ACCESS – GET

The simplest form of BIRNET is reading a single value from another BIR using the GET() function. It is used directly inside BASIC expressions:


0  WAITINIT              
10 LET LED = GET("192.168.0.200","OUT_0", 500)
20 LET VAL_44 = BIRNETSTAT
30 GOTO 10
            

Parameters:

  • IP – IP address of the remote BIR (string),
  • ID – ID of the remote object (e.g. OUT_0, TMP_1, VAL_10),
  • timeout_ms – optional timeout in milliseconds.

If the timeout is omitted, it defaults to 1000 ms. The timeout is internally clamped to the range 300–5000 ms. Even if the server responds faster, the GET() command blocks the BASIC interpreter for the full timeout duration – this guarantees that there is no internal queue of pending TCP requests and prevents network overload when the remote module is offline or restarting.

On the server side, the request is handled by a lightweight /peer endpoint. If the target BIR is not yet initialized (for example, still waiting for sensors to come online), it returns a temporary NOT_READY state and the client simply finishes the GET() without updating any value.

BIRNET SINGLE demo:

  • BIRNET_SINGLE_SERVER_demo (B) – toggles OUT_0 on the server module.
  • BIRNET_SINGLE_CLIENT_demo (A) – reads OUT_0 from the server and mirrors the LED.

MULTI-VALUE SYNC – BIRNETSYNC

For more advanced scenarios, BIRNET provides a multi-value synchronization mechanism. Instead of calling GET() multiple times, the client can fetch several values from the server in one step and update multiple local objects atomically.

The sequence is always:

  1. BIRNETSET(IP, timeout) – define which remote BIR to talk to.
  2. SERVERIDS(...) – list of remote IDs to read from the server.
  3. CLIENTIDS(...) – list of local IDs to overwrite on the client.
  4. BIRNETSYNC – perform the actual multi-value synchronization.

BIRNETSET(IP, timeout)

Configures the target BIR and timeout for all subsequent BIRNETSYNC calls:

BIRNETSET("192.168.0.200", 300)
  • IP – IP address of the remote BIR.
  • timeout – timeout in milliseconds, internally clamped to 300–5000 ms.

SERVERIDS(...)

Defines which remote IDs will be read from the server. Up to 10 IDs are supported:

SERVERIDS(OUT_0, OUT_1, OUT_2)

CLIENTIDS(...)

Defines which local IDs will be overwritten by the values from the server. The number and order of local IDs must match the remote IDs:

CLIENTIDS(OUT_0, OUT_1, OUT_2)

After this configuration, the first remote ID maps to the first local ID, the second to the second, etc.

BIRNETSYNC

Executes one synchronization step:


0 WAITINIT
10 BIRNETSET("192.168.0.200", 300)
20 SERVERIDS(OUT_0, OUT_1, OUT_2)
30 CLIENTIDS(OUT_0, OUT_1, OUT_2)
40 BIRNETSYNC
50 LET VAL_44 = BIRNETSTAT
60 GOTO 40
            

Internally, the client sends a single request to the server (endpoint /peer_multi) and receives all requested values at once. The most important property is all-or-nothing behavior:

  • If the HTTP request succeeds and the server returns the correct number of values, all listed CLIENTIDS are updated in one step.
  • If any error occurs (timeout, server not ready, HTTP error, or an unknown ID), none of the local values are changed – the previous state remains active.

This makes multi-sync safe for professional automation scenarios where partial updates could cause inconsistent states or unwanted behavior.

BIRNET MULTI demo:

  • BIRNET_MULTI_SERVER_demo – generates three random boolean outputs on the server.
  • BIRNET_MULTI_CLIENT_demo – uses BIRNETSET / SERVERIDS / CLIENTIDS / BIRNETSYNC to mirror these three outputs on the client.

BIRNETSTAT

BIRNETSTAT is a read-only numeric value that always contains the result of the last BIRNET operation (GET(...) or BIRNETSYNC). It can be used anywhere a number is expected (for example in LET or IF).

The value is set automatically to one of the following codes:

  0   - no BIRNET request has finished yet (initial state after boot or reset)
200   - last BIRNET request succeeded and the remote module returned valid data
 -1   - connection error (remote BIR could not be reached – Wi-Fi not connected or HTTP connection failed)
 -2   - remote BIR responded, but the response was empty or invalid
400   - invalid request (wrong or missing ID in the command)
404   - requested ID does not exist on the remote BIR
503   - remote BIR is not ready yet (for example its sensors or external modules are still initializing)
            

LIMITATIONS AND BEST PRACTICES

  • BIRNET communication is intended for trusted local networks (LAN). It does not replace the secure HTTPS cloud channel.
  • Both GET() and BIRNETSYNC are blocking operations: they pause the BASIC interpreter for the configured timeout on each call.
  • Always verify IDs in SERVERIDS – an unknown ID on the server side causes the entire sync to be skipped, by design, to keep your client state consistent.

The Ecosystem represents a set of highly compatible expansion modules for sensors, inputs, outputs, and basically any “object” you can imagine around an MCU-based system. Each external module does not deal with any user interaction — it is purely hardware that serves a specific sensor or function and exposes its values/channels to the BIR module.

Every module has its own 3-letter ID made of uppercase letters, for example RLY. This ID also determines the tile color generated on the dashboards according to the internal color logic. In the module firmware, you typically define the module ID, number of channels, and the module type (input / output). Each external module provides a defined number of channels of the same type. The channel count is determined by the module’s hardware and firmware, with a maximum of 8 channels per module. Up to 8 external modules can be connected to the expansion bus, resulting in up to 64 objects available to a single BIR module.

DS18B20 sensors are not counted as external modules. Up to 10 DS18B20 sensors can be connected directly to the BIR module. These sensors are not plug-and-play: if one stops communicating, the BIR module will only show an error on the corresponding tile, and whenever DS18B20 sensors are added or removed, the BIR module must be restarted. This does not apply to external modules, which are plug-and-play. They can be connected and disconnected while the system is running, and the BIR module will automatically detect the change and add or remove the modules internally and in the dashboards.

The Ecosystem is open source — anyone can design and build their own modules. This manual section describes the default reference wiring, including a reference PCB design and a basic firmware template using the Microchip PIC18F15Q40 as the module MCU.

The reference schematic and PCB were created in DesignSpark PCB 12.0. The reference firmware is built in MPLAB X using the XC8 compiler v3.00 (pack 1.27.449).

However, you can use any MCU as long as it supports UART and SPI communication.

WIRING SCHEMATIC:

Default scheme

PCB LAYOUT:

Default PCB

DOWNLOADS:

LET – Assign a numeric value

The LET instruction assigns the result of a numeric expression to a target NAME. All values in BIR BASIC are stored as floating-point numbers.

Syntax

LET NAME = numeric_expression

Rules

  • NAME can be:
    • an existing object:
      • a system ID (e.g. OUT_0, OUT_1, OUT_2, INP_3, SRV_4, SRV_5, TMP_6, module IDs such as RLY_7, ADC_11, or generic value objects VAL_x up to the current hardware limit), or
      • a user-defined name assigned to that object in the dashboard (e.g. heater, room_temp).
      System IDs and their user names always refer to the same underlying object.
    • or a standalone hidden numeric variable whose name matches the pattern:
      three uppercase letters, an underscore, and digits, e.g. AVG_1, DEL_123, MIX_42.
      If such an ID does not correspond to any existing tile, the interpreter creates a hidden variable with that name. These hidden variables exist only in program memory and are not shown as tiles on the dashboard.
  • NAME must not be a reserved keyword, function or constant (e.g. IF, THEN, TIME, DATE, DAY, ABS, RND, PI, BIRNETSTAT, …).
  • The right side must be a pure numeric expression.
  • Boolean operators (AND, OR, NOT) are not allowed inside numeric expressions. They are reserved for boolean expressions in IF conditions.
  • Expressions may include:
    • numbers (float),
    • system IDs (e.g. TMP_6, OUT_0, VAL_20, TIM_SYS) or their user-defined names (e.g. heater, room_temp),
    • hidden variables matching the AAA_123 pattern (e.g. AVG_1, MIX_1, DEL_10),
    • arithmetic operators +, -, *, /,
    • unary + and -,
    • parentheses ( ... ),
    • built-in functions such as ABS, MAX, MIN, SIN, COS, TAN, RND, LOG, LN, EXP, ROUND, FLOOR, CEIL, SQRT, …,
    • constants PI, E, BIRNETSTAT.
  • LET cannot contain an IF expression – conditions belong in a separate IF ... THEN ... line.
  • GET() can be used in LET only as the whole right-hand side, e.g. LET X = GET(...). It cannot be combined inside another expression like 1 + GET(...).

Examples

Simple assignment using system IDs:

10 LET OUT_0  = 1              # turn relay ON
20 LET VAL_20 = TMP_6          # copy current temperature into VAL_20

Assignment using user-defined names:

10 LET heater    = 1           # 'heater' is a user name mapped to OUT_0
20 LET room_temp = TMP_6       # mix of user name and system ID
30 LET LED       = 1 - LED     # toggle LED (LED is an existing object name)

Using hidden standalone variables (AAA_123 pattern):

10 LET AVG_1 = (TMP_6 + TMP_7) / 2    # hidden variable AVG_1
20 LET DEL_1 = ABS(AVG_1 - 21)        # reuse AVG_1 in another expression
30 LET MIX_1 = SIN(PI / 2) * 10       # another hidden variable

Using built-in functions and parentheses in a more complex expression:

10 LET MIX_1 = ( MAX(TMP_6, TMP_7) + ABS(VAL_20 - 3.5) ) / 2
20 LET CMD_1 = ROUND( MIX_1 / 5 )
30 LET GRF_1 = RND(50) + LOG( TMP_6 + 10 )

Using GET() inside LET (only as the whole expression):

10 LET heater = GET("192.168.0.200","OUT_0", 500)
20 LET STA_1  = BIRNETSTAT          # store last BIRNET status code into a hidden variable

IF … THEN … ELSE – Conditional branching

The IF instruction evaluates a boolean expression and, depending on the result, executes one of two possible actions:

  • the THEN branch when the condition is true,
  • the optional ELSE branch when the condition is false.

Syntax

IF boolean_expression THEN single_command
IF boolean_expression THEN single_command ELSE single_command
  • boolean_expression – condition that is evaluated to true / false.
  • single_command – exactly one BASIC command on the same line.

After THEN and ELSE you can use:

  • LET ...
  • DELAY ...
  • GOTO ...
  • GOSUB ...
  • RETURN
  • PRINT ...
  • or another nested IF ... THEN ... without its own ELSE.

Other BASIC commands (such as WAITINIT, WAITCLOUDTICK, BIRNETSET, SERVERIDS, CLIENTIDS, BIRNETSYNC, FOR, NEXT, …) must be placed on their own line – they cannot be used directly after THEN or ELSE.

Boolean expressions

Boolean expressions in IF support:

  • numeric expressions (same syntax as in LET),
  • comparison operators,
  • logical operators,
  • parentheses.

Comparison operators:

==
=
!=
<
>
<=
>=

Each side of the comparison is a numeric expression, for example:

  • TMP_6 > 21
  • (TMP_6 + TMP_7) / 2 <= 25
  • ABS(room_temp - 21) < 0.5
  • BIRNETSTAT = 200

Logical operators:

  • AND
  • OR
  • NOT

Operators AND, OR and NOT are written as words (not && or ||) and are case-insensitive (and, Or, NOT all work). Precedence:

  • NOT has the highest priority,
  • then AND,
  • then OR.

Parentheses ( ... ) can be used to override the default precedence.

Truth value without an explicit comparison:

If no comparison operator is found (for example IF heater THEN ...), the numeric expression is evaluated and treated as:

  • true if the result is not equal to 0,
  • false if the result is exactly 0.

THEN / ELSE actions

  • If the condition is true, the command after THEN is executed.
  • If the condition is false and an ELSE part is present, the command after ELSE is executed.
  • If the condition is false and there is no ELSE, the line does nothing.

Only one command is allowed in each branch – there is no support for multiple commands separated by : or similar. If you need multiple actions, use GOTO or GOSUB to jump to a block of code on separate lines.

Examples

Simple threshold:

10 IF room_temp < 21 THEN LET heater = 1
20 IF room_temp > 23 THEN LET heater = 0

Range check with ELSE:

10 IF TMP_6 >= 18 AND TMP_6 <= 24 THEN LET comfort = 1 ELSE LET comfort = 0

Boolean-style numeric expression:

10 IF heater THEN LET LED = 1 ELSE LET LED = 0   # 'heater' is true when its value != 0

Nested IF (safe form):

10 IF TMP_6 > 25 THEN IF window = 0 THEN LET heater = 0

In this form, the inner IF is the single command after THEN. The inner IF should not use its own ELSE on the same line – any ELSE always belongs to the first IF on the line. For more complex conditions, use logical operators (AND, OR, NOT) or split the logic into multiple lines.

Example program (IF THEN ELSE DEMO):

# =================
# IF THEN ELSE DEMO
# =================

10 LET SWITCH = 1
20 IF (VALUE = 32 OR VALUE = 64 OR VALUE = 128) AND SWITCH = 1 THEN LET LED1 = 1 ELSE LET LED1 = 0
30 IF (VALUE = 10 OR VALUE = 20 OR VALUE = 30) AND SWITCH = 1 THEN LET LED2 = 1 ELSE LET LED2 = 0
40 IF (VALUE = 11 OR VALUE = 22 OR VALUE = 33) AND SWITCH = 1 THEN LET LED3 = 1 ELSE LET LED3 = 0
50 DELAY 100
60 GOTO 20

GOTO – Unconditional jump

The GOTO instruction jumps to another line in the program. The target is specified by its line number – the number at the beginning of a BASIC line.

Syntax

GOTO lineNumber

Rules

  • lineNumber must match the number at the beginning of an existing BASIC line (for example 10, 100, 250).
  • The dashboard check verifies that all GOTO targets point to existing lines before the program is uploaded. If a GOTO to a non-existing line somehow reaches the interpreter, it is ignored at runtime and execution continues with the next line.
  • GOTO can be used:
    • as a standalone command (e.g. 20 GOTO 100),
    • after THEN or ELSE in an IF line (e.g. 10 IF VALUE > 0 THEN GOTO 100).
  • Only one command is allowed on each line – you cannot write multiple commands separated by : or similar.

Example program (IF AS CASE DEMO)

# ===============
# IF AS CASE DEMO
# ===============

# Replace select case statement

0  LET SPEED = 100
10 LET VALUE = RND()
20 DELAY SPEED
30 IF VALUE >= 0.0 AND VALUE < 0.2 THEN GOTO 1000
40 IF VALUE >= 0.2 AND VALUE < 0.4 THEN GOTO 2000
50 IF VALUE >= 0.4 AND VALUE < 0.6 THEN GOTO 3000
60 GOTO 10

1000 LET LED1 = 1 - LED1
1010 GOTO 10

2000 LET LED2 = 1 - LED2
2010 GOTO 10

3000 LET LED3 = 1 - LED3
3010 GOTO 10

GOSUB / RETURN – Subroutines

GOSUB is used to call a subroutine located at another line number. The interpreter remembers where it came from, jumps to the subroutine, and later RETURN brings execution back to the line after the last GOSUB.

Syntax

GOSUB lineNumber
RETURN

Rules for GOSUB

  • lineNumber must be an existing line in the program. The dashboard check validates all GOSUB targets before upload and reports calls to missing lines as errors.
  • When GOSUB is executed:
    • the current line position is pushed onto an internal call stack,
    • execution jumps to the target line (the subroutine start).
  • Subroutines can call other subroutines (nested GOSUB calls).
  • The maximum nesting depth is 20 active GOSUB levels. If this limit is reached, further GOSUB calls no longer enter the subroutine (the call is effectively ignored and execution stays in the main flow).
  • GOSUB can also be used after THEN or ELSE in an IF line.

Rules for RETURN

  • RETURN should be placed at the end of a subroutine.
  • When RETURN is executed:
    • the last saved position from GOSUB is taken from the stack,
    • execution continues with the line after that GOSUB.
  • If RETURN is executed when no GOSUB is active, it has no effect and execution simply continues with the next line.
  • If execution falls out of a subroutine without reaching RETURN, it continues into the following lines. For clarity, it is recommended to always end subroutines with an explicit RETURN.

Basic subroutine example

10 GOSUB 100
20 DELAY 1000
30 GOTO 10

100 LET LED = 1
110 DELAY 200
120 LET LED = 0
130 RETURN

GOSUB used with IF

10 IF TMP_6 > 25 THEN GOSUB 100
20 GOTO 10

100 LET heater = 1
110 RETURN

In the second example, the subroutine at line 100 is called only when the condition in line 10 is true. After RETURN, execution continues at line 20.

Example program (GOSUB DEMO)

# =========
# GOSUB DEMO
# =========

10 LET LED1 = 1                    # LEVEL 0 
20 GOSUB 100
30 DELAY 500
40 LET LED1 = 0
50 DELAY 500
60 GOTO 10

100 IF LEVEL == 0 THEN RETURN      # LEVEL 1
110 LET LED2 = 1
120 IF LEVEL == 2 THEN GOSUB 200
130 DELAY 500
140 LET LED2 = 0
150 DELAY 500
160 GOTO 100

200 LET LED3 = 1                   # LEVEL 2
210 DELAY 500
220 LET LED3 = 0
230 DELAY 500
240 IF LEVEL == 1 THEN RETURN
250 GOTO 200

FOR / TO / STEP / NEXT – Counting loops

The FOR loop repeats a block of code a given number of times. The loop variable is numeric and is updated automatically on each NEXT.

Syntax

FOR var = start TO end
FOR var = start TO end STEP step_value
...
NEXT
NEXT var

Rules

  • var is a numeric NAME:
    • a system ID (e.g. VAL_10, OUT_0, TMP_6),
    • or a user-defined object name (e.g. LOOP, SPEED, INC1),
    • or a hidden variable matching the AAA_123 pattern (e.g. LOP_1).
    The loop variable is stored like any other numeric object – you can read it in expressions inside the loop.
  • start, end and step_value are numeric expressions (floats are supported).
  • STEP is optional:
    • if omitted, STEP 1 is used by default,
    • a negative STEP is allowed (counting down),
    • a very small or zero STEP is not recommended, because it makes the loop effectively run only once.
  • Each FOR must have a matching NEXT. Nested loops are supported and must be properly paired in the source code.
  • NEXT can optionally specify the loop variable (e.g. NEXT LOOP). If present, the name must match the corresponding FOR. If omitted, the nearest active FOR is used.
  • Inside the loop you can use any valid BASIC commands (LET, IF, DELAY, GOSUB, PRINT, etc.).

Simple examples

Simple loop 0–9:

10 FOR LOOP = 0 TO 9
20   LET OUT_0 = 1
30   DELAY 200
40   LET OUT_0 = 0
50   DELAY 200
60 NEXT LOOP

Loop with STEP and a helper variable:

10 FOR LOOP = 0 TO 20 STEP 0.5
20   LET INVERSE = 20 - LOOP
30   LET LED = 1
40   DELAY LOOP
50   LET LED = 0
60   DELAY INVERSE
70 NEXT LOOP

Example program (FOR NEXT STEP DEMO)

# ==================
# FOR NEXT STEP DEMO
# ==================

0   LET CYCLES = 0
1   LET FAST_STEP = 5

10  FOR INC1 = 2.5 TO -2.5 STEP -0.1
20    FOR INC2 = 0 TO 200 STEP FAST_STEP
30      LET LED1 = 1
40      DELAY 50
50      LET LED1 = 0
60      DELAY 50
70    NEXT INC2
80    LET FLAG = FLAG + 1.5
90    DELAY 1000
100 NEXT INC1

110 LET LED1 = 1
120 LET FLAG = 0
130 DELAY 10000
140 LET CYCLES = CYCLES + 1
150 GOTO 10

DELAY – Pause execution

The DELAY instruction pauses program execution for a specified number of milliseconds.

Syntax

DELAY ms_expression

Rules

  • ms_expression is a numeric expression evaluated to a delay in milliseconds (floats are allowed and are rounded to the nearest millisecond).
  • If the result is zero or negative, the delay is skipped and execution continues with the next line.
  • Internally, DELAY uses a time anchor to keep the loop period stable: successive DELAY calls with the same value create a regular timing pattern (useful in main loops).
  • After a WAITCLOUDTICK, the next DELAY automatically re-synchronizes its internal time anchor to the current moment, so timing continues smoothly from the cloud tick.
  • During DELAY the BASIC interpreter task is paused and does not execute other BASIC lines, but the BIR firmware and other internal tasks continue running normally.

Example

10 LET MS = 200
20 LET LED = 1
30 DELAY MS
40 LET LED = 0
50 DELAY MS
60 GOTO 20

WAITINIT – Wait for initialization

The WAITINIT instruction waits until the module finishes initialization and all connected sensors (DS18B20 and external modules) have their first valid readings.

Syntax

WAITINIT

Rules

  • WAITINIT takes no arguments.
  • It is usually placed at the beginning of the program (often on line 0), but it can be used on any line. If initialization is already finished, WAITINIT returns immediately.
  • While initialization is still in progress, the BASIC interpreter repeatedly stays on the WAITINIT line and sleeps for short intervals, so the rest of the program does not run yet.
  • Without WAITINIT, the BASIC program starts immediately after boot and may use initial sensor values that are still zero or otherwise invalid.
  • WAITINIT must be used as a standalone command on its own line. It cannot be placed after THEN or ELSE in an IF statement.

Example (WAITINIT demo)

# =============
# WAITINIT demo
# =============

# For this demo, at least one DS18B20 sensor is required.
# The WAITINIT command waits for the initialization to complete
# and for the first valid readings from all DS18B20 and external sensors.

0 WAITINIT
1 LET FIRST_TEMP = TEMP

10 LET LED = 1 - LED
20 DELAY 500
30 GOTO 10

WAITCLOUDTICK – Wait for the next cloud tick

The WAITCLOUDTICK instruction blocks the BASIC program until the cloud server signals a new synchronization tick (when the online dashboard is active).

Syntax

WAITCLOUDTICK

Rules

  • WAITCLOUDTICK takes no arguments.
  • Execution stops on the line with WAITCLOUDTICK until the next cloud tick arrives. The BASIC interpreter repeatedly sleeps in short intervals while it is waiting.
  • When a new cloud tick arrives, WAITCLOUDTICK unblocks, the internal timing anchor for DELAY is re-synchronized, and execution continues on the next line.
  • If the online dashboard is closed and the module detects that the client is offline, the BASIC program remains parked on the WAITCLOUDTICK line until a client reconnects.
  • WAITCLOUDTICK must be used as a standalone command on its own line. It cannot be placed after THEN or ELSE in an IF statement.
  • A common pattern is to perform some action (e.g. blink an LED), then wait for the next cloud tick and optionally count successful ticks in a dedicated value.

Example (WAITCLOUDTICK demo)

# ====================
# WAIT CLOUD TICK demo
# ====================

# Blinks the LED for 0.5 s, then waits for the next cloud tick.
# WAITCLOUDTICK blocks until the server cycle signals a new tick.
# Each tick increments CLOUD-TICKS to track successful cloud syncs.
# If the online dashboard is closed, the BIR module detects the client is offline
# and parks the BASIC program on line 40 (WAITCLOUDTICK).

10 LET LED = 1
20 DELAY 500
30 LET LED = 0
40 WAITCLOUDTICK
50 LET CLOUD-TICKS = CLOUD-TICKS + 1
60 GOTO 10

PRINT – Show a text label for a numeric value

The PRINT instruction attaches a short text label (a "mask") to a numeric value for display on the dashboard. The underlying variable always keeps its real floating-point value.

Syntax

PRINT "text", ID

Rules

  • text is a string literal enclosed in double quotes, up to 10 characters. Longer texts are automatically truncated to the first 10 characters.
  • ID must be an existing object ID exactly as shown on the tile (e.g. VAL_10, TMP_6, OUT_0, Door if it is the real ID, …).
  • If text is a non-empty string, that text is displayed instead of the raw numeric value on the corresponding tile.
  • If text is an empty string (""), the text mask is removed and only the numeric value is shown again.
  • The numeric value of the object itself always keeps its true floating-point value and can be used in all expressions and conditions, regardless of the active text mask.
  • PRINT can also be used after THEN or ELSE in an IF line as a single command.

Example (PRINT demo)

# ==========
# PRINT demo
# ==========

# This demo shows how PRINT adds a text "mask" to a numeric value.
# If the first parameter is a non-empty string (up to 10 characters), that text is displayed instead of the raw value.
# If the first parameter is an empty string (""), the text is removed and only the numeric value is shown.
# The variable itself always keeps its real floating-point value for all calculations and conditions.

10 LET Door = 1 - Door
20 IF Door = 1 THEN PRINT "Open", Door ELSE PRINT "Closed", Door
30 LET DoorValue = Door
40 LET INC = INC + 1
50 IF INC >= 5 THEN GOSUB 100
60 WAITCLOUDTICK
70 GOTO 10

# Reset string mask back to the raw numeric value
100 PRINT "", Door
110 LET INC = 0
120 RETURN

TIME, DATE, DAY – System parameters

BIR BASIC provides three read-only system parameters for working with time and calendar: TIME, DATE and DAY. They can be used in expressions and IF conditions just like numeric values.

  • TIME – current time of day as a floating-point number (hours with fractional part).
    For example, 17:30 is represented as 17.5, 6:15 as 6.25.
  • DATE – current date as day + month/100.
    For example, 9 January is 9.01, 31 December is 31.12.
  • DAY – current day of week as an integer: SUN=0, MON=1, TUE=2, WED=3, THU=4, FRI=5, SAT=6.
  • TIME, DATE and DAY are read-only system parameters. They cannot be used on the left side of LET or as loop variables in FOR.
  • Their names are reserved keywords – you cannot rename objects to TIME, DATE or DAY on the dashboard.

In IF conditions, special readable forms are supported and automatically converted to numeric expressions by the dashboard check:

  • TIME >= 12:02 AND TIME < 12:03 → comparison with a time range,
  • DATE = 9.1 → comparison with the numeric date value,
  • DAY = FRI → comparison with the numeric day-of-week code.

Note about TIME comparisons:

  • TIME changes every second and is stored as a floating-point value.
  • Using an exact equality such as TIME = 17:31 is not recommended, because the internal value and the rounded literal almost never match exactly.
  • Always use a time range in conditions, for example:
    IF TIME >= 17:31 AND TIME < 17:32 THEN ...

Example (DATETIME demo)

# =============
# DATETIME demo
# =============

10 IF TIME >= 12:02 AND TIME < 12:03 THEN LET LED1 = 1 ELSE LET LED1 = 0
20 IF DATE = 9.1  THEN LET LED2 = 1 ELSE LET LED2 = 0
30 IF DAY = FRI   THEN LET LED3 = 1 ELSE LET LED3 = 0
40 DELAY 1000
50 GOTO 10

Math functions and constants

BIR BASIC supports a set of built-in math functions and constants that can be used in any numeric expression (in LET, IF, FOR, etc.). All function names and constants are case-insensitive.

Functions:

  • ABS(x) – absolute value
  • MIN(a, b), MAX(a, b) – minimum / maximum of two values
  • MOD(a, b) – remainder after division (floating-point modulus)
  • SIGN(x) – returns -1 for negative, 0 for zero, 1 for positive values
  • ROUND(x), FLOOR(x), CEIL(x) – rounding, floor and ceiling
  • SQRT(x) – square root
  • LOG(x) – base-10 logarithm
  • LN(x) – natural logarithm (base E)
  • EXP(x) – ex
  • SIN(x), COS(x), TAN(x) – trigonometric functions, x is in radians
  • RND() – random value in the range 0..1
  • RND(max) – random value in the range 0..max

Constants:

  • PI – π (≈ 3.14159)
  • E – Euler's number (≈ 2.71828)
  • BIRNETSTAT – last BIRNET status code (numeric value that can be used in expressions and IF conditions).

Example (Math DEMO)

# =========
# Math DEMO
# =========

# -----------------------------------------
# BLOCK A: Basic arithmetic (std precedence)
# -----------------------------------------
1000 LET VAL_14 = 2 + 3 * 4          # 14
1010 LET VAL_15 = 10 / 2 * 3         # 15
1020 LET VAL_16 = 5 - 2 * 3 + 4 * 2  # 7
1030 LET VAL_17 = -5 + 2 * 3         # 1
1040 LET VAL_18 = --5 + -+-+3        # 8

# -----------------------------------------
# BLOCK B: Parentheses (explicit priority)
# -----------------------------------------
1100 LET VAL_19 = (5 + 3) * 2        # 16
1110 LET VAL_20 = (10 - 4) / (2 + 1) # 2
1120 LET VAL_21 = (3 + (2 * (1 + 1)))# 7

# -----------------------------------------
# BLOCK C: Math functions & constants
# -----------------------------------------
1200 LET VAL_22 = ABS(-5)            # 5
1210 LET VAL_23 = SQRT(81)           # 9
1220 LET VAL_24 = MIN(2, 8)          # 2
1230 LET VAL_25 = MAX(2, 8)          # 8
1240 LET VAL_26 = MOD(17, 5)         # 2
1250 LET VAL_27 = ROUND(3.6)         # 4
1260 LET VAL_28 = FLOOR(3.8)         # 3
1270 LET VAL_29 = CEIL(3.2)          # 4
1280 LET VAL_30 = SIGN(-42)          # -1
1290 LET VAL_31 = LOG(100)           # 2
1300 LET VAL_32 = LN(2.71828)        # 1(≈)
1310 LET VAL_33 = EXP(2)             # 7.389(≈)
1320 LET VAL_34 = SIN(PI / 2)        # 1
1330 LET VAL_35 = COS(PI)            # -1
1340 LET VAL_36 = TAN(PI / 4)        # 1
1350 LET VAL_37 = RND()              # 0..1
1360 LET VAL_38 = RND(100)           # 0..100
1370 LET VAL_39 = E                  # 2.71828...

# -----------------------------------------
# BLOCK D: Variables combined with expressions
# -----------------------------------------
1400 LET OUT_0 = 1
1410 LET VAL_40 = 5 * OUT_0 + VAL_22 / 2           # 7.5
1420 LET VAL_41 = (VAL_40 + SQRT(VAL_23)) / 2      # 5.25

# -----------------------------------------
# BLOCK E: IF / THEN / ELSE with math
# -----------------------------------------
1500 LET VAL_41 = (VAL_40 + SQRT(VAL_23)) / 2      # z předchozího bloku
1510 IF VAL_41 > 10 THEN LET OUT_0 = 1 ELSE LET OUT_0 = 0
1520 IF ABS(SIN(PI/2) - 1) < 0.001 THEN LET OUT_1 = 1
1530 IF RND() > 0.5 THEN LET OUT_2 = 1 ELSE LET OUT_2 = 0

# -----------------------------------------
# BLOCK F: Loops and DELAY with expressions
# -----------------------------------------
1600 LET VAL_43 = 1000
1610 FOR VAL_44 = 1 TO 5 STEP 1
1620   LET OUT_1 = 1
1630   DELAY 1000 + VAL_43 / 2        # 1500 ms
1640   LET OUT_1 = 0
1650   DELAY 1000 + VAL_43 / 2        # 1500 ms
1660 NEXT VAL_44

Ecosystem

RLY module

The RLY (relay) module is designed for safe, galvanically isolated power switching of electrical devices. It uses LEG-5 relays, rated for up to 15 A and 230 V. The module provides four independent channels. The MCU that communicates with the BIR module over the bus is powered from 3.3 V, while the relay coils are driven from 5 V.

RLY module wiring

Description of digital input modules will be placed here.

Description of analog input modules will be placed here.

...

Demo

Live demo description and access instructions will be placed here.

Demo Dashboard

Contact

Contact information and project details will be placed here.