Back to Blog
3 min read

Christmas Eve Automation: Building a Holiday Light Controller with Azure IoT

What better way to spend Christmas Eve than building a smart holiday light controller? This project combines Azure IoT Hub, a Raspberry Pi, and some festive creativity.

Project Overview

We’ll build a system that:

  • Controls holiday lights via Azure IoT Hub
  • Responds to voice commands through a simple API
  • Runs pre-programmed light shows
  • Monitors power consumption

Hardware Requirements

  • Raspberry Pi 4 (or Zero 2 W)
  • Relay module (4-channel)
  • Smart power monitoring plug (optional)
  • Holiday lights (LED recommended for safety)

Azure IoT Hub Setup

# Create IoT Hub
az iot hub create \
  --name holiday-lights-hub \
  --resource-group holiday-rg \
  --sku S1

# Register device
az iot hub device-identity create \
  --hub-name holiday-lights-hub \
  --device-id raspberry-pi-lights

Raspberry Pi Code

import asyncio
import json
from azure.iot.device.aio import IoTHubDeviceClient
from azure.iot.device import MethodResponse
import RPi.GPIO as GPIO

# GPIO setup for relay control
RELAY_PINS = {
    "tree": 17,
    "window": 27,
    "outdoor": 22,
    "star": 23
}

GPIO.setmode(GPIO.BCM)
for pin in RELAY_PINS.values():
    GPIO.setup(pin, GPIO.OUT)
    GPIO.output(pin, GPIO.LOW)

class HolidayLightController:
    def __init__(self, connection_string: str):
        self.client = IoTHubDeviceClient.create_from_connection_string(connection_string)
        self.light_states = {name: False for name in RELAY_PINS}

    async def connect(self):
        await self.client.connect()
        self.client.on_method_request_received = self.handle_method
        print("Connected to Azure IoT Hub")

    async def handle_method(self, method_request):
        """Handle direct method calls from IoT Hub."""
        response_payload = {"result": "success"}
        status = 200

        if method_request.name == "setLight":
            light_name = method_request.payload.get("light")
            state = method_request.payload.get("state")

            if light_name in RELAY_PINS:
                self.set_light(light_name, state)
                response_payload["message"] = f"{light_name} set to {state}"
            else:
                status = 400
                response_payload = {"error": f"Unknown light: {light_name}"}

        elif method_request.name == "runShow":
            show_name = method_request.payload.get("show", "twinkle")
            asyncio.create_task(self.run_light_show(show_name))
            response_payload["message"] = f"Started {show_name} show"

        elif method_request.name == "allOff":
            self.all_lights_off()
            response_payload["message"] = "All lights off"

        response = MethodResponse.create_from_method_request(
            method_request, status, response_payload
        )
        await self.client.send_method_response(response)

    def set_light(self, name: str, state: bool):
        pin = RELAY_PINS[name]
        GPIO.output(pin, GPIO.HIGH if state else GPIO.LOW)
        self.light_states[name] = state

    def all_lights_off(self):
        for name in RELAY_PINS:
            self.set_light(name, False)

    async def run_light_show(self, show_name: str):
        """Run pre-programmed light shows."""
        if show_name == "twinkle":
            for _ in range(20):
                for name in RELAY_PINS:
                    self.set_light(name, True)
                    await asyncio.sleep(0.2)
                    self.set_light(name, False)

        elif show_name == "wave":
            for _ in range(10):
                for name in RELAY_PINS:
                    self.set_light(name, True)
                    await asyncio.sleep(0.5)
                for name in reversed(list(RELAY_PINS.keys())):
                    self.set_light(name, False)
                    await asyncio.sleep(0.5)

async def main():
    controller = HolidayLightController(
        "HostName=holiday-lights-hub.azure-devices.net;DeviceId=..."
    )
    await controller.connect()

    # Keep running
    while True:
        await asyncio.sleep(1)

asyncio.run(main())

Controlling via Azure Function API

[Function("ControlLights")]
public async Task<IActionResult> ControlLights(
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req)
{
    var command = await req.ReadFromJsonAsync<LightCommand>();

    var serviceClient = ServiceClient.CreateFromConnectionString(_connectionString);

    var method = new CloudToDeviceMethod(command.Action);
    method.SetPayloadJson(JsonSerializer.Serialize(command.Parameters));

    var response = await serviceClient.InvokeDeviceMethodAsync(
        "raspberry-pi-lights", method);

    return new OkObjectResult(response.GetPayloadAsJson());
}

A fun project that combines cloud, IoT, and holiday spirit. Merry Christmas Eve!

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.