Thermostat: Reacting to Sensor Readings

A thermostat keeps a heater on or off as temperature readings change. This console simulation demonstrates beliefs, a spawned goal, and reactive plans scoped to an encapsulated goal. It also uses a dead band: turn on below 19, turn off above 21, and keep the existing state between those limits. This avoids switching at every small change around a single threshold.

Run the example

Use an ASTRA 2.6.1 project from the Quick Start. Put the following files in src/main/astra. Each file name must match its agent class.

Thermostat.astra

Download Thermostat.astra

agent Thermostat {
    module Console C;
    module System S;

    types heating {
        formula temperature(int);
        formula heater(string);
        formula ready();
        formula processed(int);
        formula stopped();
    }

    initial heater("off");

    plan +!main(list args) {
        !!control();
        wait(ready());
        !sample(17);
        !sample(20);
        !sample(22);
        +stopped();
        S.exit();
    }

    plan +!sample(int T) {
        if (temperature(int Old)) -temperature(Old);
        +temperature(T);
        wait(processed(T));
        -processed(T);
    }

    goal +!control() <: stopped() {
        body {
            +ready();
        }

        plan +temperature(int T) : heater(string State) {
            if (T < 19 & State == "off") {
                -heater(State);
                +heater("on");
            } else {
                if (T > 21 & State == "on") {
                    -heater(State);
                    +heater("off");
                }
            }
            query(heater(string Now));
            C.println("temperature=" + T + ", heater=" + Now);
            +processed(T);
        }
    }
}

From the project folder, run:

mvn -Dastra.main=Thermostat

Expected output

[main] temperature=17, heater=on
[main] temperature=20, heater=on
[main] temperature=22, heater=off

How it works

heater(...) represents the current setting; temperature(...) represents the latest reading. !!control() starts a separate intention so that the main plan can supply readings while the controller remains active. The controller’s goal condition is stopped(), so finishing its initial body does not end it.

The inner +temperature(...) plan is active only while the controller goal is running. It decides whether to change the heater and then adds processed(T). The sample plan waits for that acknowledgement before supplying another reading. This makes the simulation reproducible without depending on arbitrary sleeps.

At 20 degrees, neither switching condition applies, so the heater stays on. A real sensor could supply the readings through messages or a Java module event.

Try a change

Add readings of 18, 19, 21 and 23. Predict the heater state before running the program. Then change the thresholds and observe how the dead band changes. Replace the scripted readings with message events to turn the controller into a separate agent.

See Encapsulated Goals, Beliefs and Inference and Creating Multiple Agents.