Skip to content

Cookbook: Real-time IoT Thermostat ​

In a standard stack, building an IoT backend is a nightmare of infrastructure. You have to set up cron jobs or message brokers (like Kafka/RabbitMQ) just to poll sensor data, write complex state machines to ensure the heater doesn't fight the AC, and manage race conditions when a user tries to override the temperature from their phone.

In Carotene, time-based execution and state management are native primitives. You don't need external polling servers; you just use a loop block and let the Vapor Sandbox guarantee the state transitions.

Here is a complete, real-time smart thermostat backend.

1. The Architecture Blueprint ​

We start by defining the physical state of the device. Because an HVAC system physically cannot be heating and cooling at the exact same time, we define a strict flow state machine to prevent catastrophic hardware commands.

dart
domain HomeAutomation {
  
  // 1. The Hardware Guardrail
  // Mathematically prevents the AC and Heater from running simultaneously.
  flow HVACMode {
    on Change {
      Idle -> Heating
      Idle -> Cooling
      Heating -> Idle
      Cooling -> Idle
    }
  }

  // 2. The Device State
  model Thermostat {
    id: UUID
    currentTemp: Float
    targetTemp: Float
    mode: HVACMode
    lastPing: Date
  }

  store Thermostat {
    model: Thermostat
  }
}

2. The Continuous Execution Loop ​

Instead of setting up external cron jobs, Carotene allows you to define background processes natively using the loopkeyword.

This loop runs every minute, reads external weather data to optimize energy usage, and adjusts the physical HVAC mode.

dart
backend IoTGateway {

  // We declare an external integration to fetch local weather
  integration WeatherAPI {
    GetOutsideTemp(zipcode: String) -> Float
  }

  // ==========================================
  // AUTONOMIC LOOP (Runs every 1 minute)
  // ==========================================
  loop OptimizeClimate {
    config {
      interval: "1m"
    }
    reads store.Thermostat
    updates store.Thermostat
    calls integration.WeatherAPI
    triggers store.Thermostat.mode.Change

    implements {
      device = store.Thermostat.find("device_1") // Assuming a single-home setup for simplicity
      outsideTemp = integration.WeatherAPI.GetOutsideTemp("90210")
      
      // The Generative Operator handles the complex thermodynamic prediction logic
      // rather than us writing nested IF statements for insulation, delta-T, etc.
      idealMode = @("Compare the device's currentTemp to the targetTemp, factoring in the outsideTemp. Return 'Heating', 'Cooling', or 'Idle' to reach the target most efficiently.")

      // Only apply the change if a transition is needed
      if (device.mode != idealMode) {
        device.mode = idealMode
      }
      
      device.lastPing = Time.now()
    }
  }
}

3. The User Override (Mobile App API) ​

We also need a standard API endpoint so the user can open their phone and change the target temperature manually.

dart
backend MobileAPI {

  // ==========================================
  // MANUAL OVERRIDE
  // ==========================================
  function SetTargetTemperature(deviceId: store.Thermostat.id, newTarget: store.Thermostat.targetTemp) {
    requires session.isAuthenticated
    updates store.Thermostat

    implements {
      device = store.Thermostat.find(deviceId)
      
      // Hard constraint: Don't freeze or boil the house
      if (newTarget < 50.0 || newTarget > 90.0) {
        throws "Target temperature out of safe bounds"
      }

      device.targetTemp = newTarget
      return true
    }
  }
}

4. The Proof (Testing Time and State) ​

To test this, we need to prove two things: that the AI correctly predicts the needed HVAC mode based on outside weather, and that our strict flow prevents illegal state changes.

dart
// Test 1: AI Thermodynamic Logic
test "Turns on Heating when house is cold and outside is freezing" {
  config { sandbox: EmbeddedWASM }

  // 1. Mock the device currently sitting idle at 60 degrees
  given mock store.Thermostat { 
    id: "device_1", 
    currentTemp: 60.0, 
    targetTemp: 72.0, 
    mode: Idle 
  }
  
  // 2. Mock the Weather API returning a freezing temperature
  mock integration.WeatherAPI.GetOutsideTemp -> 30.0

  // 3. Manually trigger one iteration of the loop
  trigger loop OptimizeClimate
  
  // 4. Assert the AI correctly chose to trigger the Heater
  asserts store.Thermostat.find("device_1").mode == Heating
}

// Test 2: Hardware Safety Guardrails
test "Rejects unsafe manual temperature overrides" {
  given mock session { isAuthenticated: true }
  given mock store.Thermostat { id: "device_1", targetTemp: 72.0 }
  
  // Assert the manual override fails with our exact error message
  asserts SetTargetTemperature("device_1", 100.0) throws "Target temperature out of safe bounds"
}

5. Compile and Deploy ​

When you run carrot build:

  1. The compiler maps the loop to a highly optimized background worker in Go or TypeScript.
  2. It generates the REST/RPC endpoint for the mobile app.
  3. It uses the LLM to write the predictive logic comparing currentTemp, targetTemp, and outsideTemp.
  4. It runs the Sandbox tests to mathematically guarantee the house will actually start heating when it gets cold, and that a user cannot set their app to 100 degrees.