đŸ“± Level 5 – App Communication

Project 5.12: "Integration N1 to N5"

 

What you’ll learn

  • ✅ Integration: Combine sensors, actuators, app, routines, and preferences into one system.
  • ✅ Home automation: Simulate lights, fans, and alerts controlled by app and sensors.
  • ✅ Multi‑function service robot: Switch between assistant, monitor, and game modes.
  • ✅ Security monitoring: Detect rain, soil, or sound and send alerts.
  • ✅ Interactive game: Use app commands and sensors for multiplayer fun.
  • ✅ Creative project: Leave space for students to invent their own integration.

Key ideas

  • Short definition: Integration means connecting all parts so they work together.
  • Real‑world link: Smart homes and robots combine sensors, apps, and routines to serve people in many ways.

Blocks glossary (used in this project)

  • Digital inputs: Rain sensor, sound sensor.
  • Analog input: Soil moisture sensor.
  • DHT11: Temperature sensor.
  • Digital outputs: Motors, LED, buzzer.
  • OLED shows: Display status and modes.
  • Bluetooth callback: Receive app commands.
  • Variables + lists: Store routines, preferences, and modes.
  • Serial println: Send “KEY:VALUE” messages for app display.

What you need

PartHow many?Pin connection
D1 R321USB cable (30 cm)
DHT111Signal → Pin 26, VCC, GND
Soil sensor1Signal → Pin 32 (ADC), VCC, GND
Raindrop sensor1DO → Pin 35, VCC, GND
Sound sensor1DO → Pin 34, VCC, GND
L298N motor driver + motors1IN1→18, IN2→19, IN3→5, IN4→23
LED module1Signal → Pin 13, VCC, GND
Buzzer module1Signal → Pin 23, VCC, GND
OLED SSD1306 128×641I2C: SCL→22, SDA→21, VCC, GND
Smartphone (Bluetooth)1Connect to “Clu‑Bots”

Before you start

  • USB cable is plugged in
  • Serial monitor is open
  • Test print shows:
print("Ready!")  # Confirm serial is working

Microprojects 1–5

Microproject 5.12.1 – Home automation system (simulated)

Goal: Control LED (light) and buzzer (alarm) with app commands.
Blocks used:

  • Digital outputs: LED, buzzer.
  • Serial println: Print “HOME:LIGHT_ON/OFF”, “HOME:ALARM_ON/OFF”.

MicroPython code:

import machine  # Import machine for pins

led = machine.Pin(13, machine.Pin.OUT)  # LED on Pin 13
buzzer = machine.Pin(23, machine.Pin.OUT)  # Buzzer on Pin 23
print("Microproject 5.12.1: Home automation outputs ready")  # Confirm setup

def home_light(on):  # Function to control light
    led.value(1 if on else 0)  # Set LED ON if on=True else OFF
    print("HOME:LIGHT_ON" if on else "HOME:LIGHT_OFF")  # Print status

def home_alarm(on):  # Function to control alarm
    buzzer.value(1 if on else 0)  # Set buzzer ON if on=True else OFF
    print("HOME:ALARM_ON" if on else "HOME:ALARM_OFF")  # Print status

Reflection: Simple ON/OFF commands simulate smart home devices.
Challenge:

  • Easy: Add “HOME:FAN_ON/OFF”.
  • Harder: Add automatic alarm when rain sensor triggers.

Microproject 5.12.2 – Multi‑function service robot

Goal: Switch between modes: ASSISTANT, MONITOR, GAME.
Blocks used:

  • Variable mode: Store current mode.
  • Serial println: Print “MODE:
”.

MicroPython code:

mode = "ASSISTANT"  # Start in assistant mode
print("Microproject 5.12.2: Mode initialized to", mode)  # Confirm start mode

def set_mode(new_mode):  # Function to change mode
    global mode  # Use global mode variable
    mode = new_mode  # Update mode
    print("MODE:", mode)  # Print new mode

Reflection: Modes let one robot do many jobs—like switching hats.
Challenge:

  • Easy: Add “MODE:SECURITY”.
  • Harder: Add OLED display showing current mode.

Microproject 5.12.3 – Security and monitoring system with app

Goal: Use rain, soil, and sound sensors to trigger alerts.
Blocks used:

  • Digital/analog inputs: Rain, soil, sound.
  • Serial println: Print “ALERT:RAIN/DRY_SOIL/NOISE”.

MicroPython code:

import dhtx  # Import DHT11 library
import machine  # Import machine for ADC and Pin

sensor = dhtx.DHT11(26)  # DHT11 on Pin 26
adc32 = machine.ADC(machine.Pin(32))  # Soil sensor on Pin 32
adc32.atten(machine.ADC.ATTN_11DB)  # Configure ADC attenuation
adc32.width(machine.ADC.WIDTH_12BIT)  # Use 12-bit resolution
rain = machine.Pin(35, machine.Pin.IN)  # Raindrop sensor on Pin 35
sound = machine.Pin(34, machine.Pin.IN)  # Sound sensor on Pin 34
print("Microproject 5.12.3: Sensors ready")  # Confirm setup

temp_c = sensor.temperature()  # Read temperature
soil_raw = adc32.read()  # Read soil raw value
soil_pct = int((4095 - soil_raw) * 100 / 4095)  # Map soil to percent
rain_state = rain.value()  # Read rain state
sound_state = sound.value()  # Read sound state

print("TEMP:", temp_c)  # Print temperature
print("SOIL(%):", soil_pct)  # Print soil percent
print("RAIN:", rain_state)  # Print rain state
print("SOUND:", sound_state)  # Print sound state

if rain_state == 1:  # If rain detected
    print("ALERT:RAIN")  # Print rain alert
if soil_pct < 25:  # If soil too dry
    print("ALERT:DRY_SOIL")  # Print dry soil alert
if sound_state == 1:  # If loud sound detected
    print("ALERT:NOISE")  # Print noise alert

Reflection: Multiple sensors make monitoring smarter—alerts show risks clearly.
Challenge:

  • Easy: Add “ALERT:HEAT” if temp > 32.
  • Harder: Add combined “ALERT:FLOOD_RISK” if rain=1 and soil>80.

Microproject 5.12.4 – Multi‑device interactive game

Goal: Use app commands and sensors for a multiplayer game.
Blocks used:

  • Bluetooth callback: Receive commands.
  • Variables: Track scores.
  • Serial println: Print “GAME:
”.

MicroPython code:

scoreA, scoreB = 0, 0  # Initialize scores
turn = "A"  # Start with Player A
print("Microproject 5.12.4: Game initialized")  # Confirm setup

def game_score(player):  # Function to add score
    global scoreA, scoreB, turn  # Use global variables
    if player == "A":  # If Player A scores
        scoreA += 1  # Increase A score
        print("SCORE_A:", scoreA)  # Print A score
    else:  # If Player B scores
        scoreB += 1  # Increase B score
        print("SCORE_B:", scoreB)  # Print B score
    turn = "B" if turn == "A" else "A"  # Switch turn
    print("TURN:", turn)  # Print whose turn it is

Reflection: Multiplayer games make robots social—turns keep it fair.
Challenge:

  • Easy: Add OLED display showing scores.
  • Harder: Add “WINNER?” command to print who leads.

Microprojects 1–5

Microproject 5.12.5 – Free creative project

Goal: Build “Garden Guardian”: a creative combo that watches rain/soil, announces status, and plays a celebratory sound/light pattern when conditions are ideal.
Blocks used:

  • Inputs: DHT11, soil ADC, rain DO.
  • Outputs: LED, buzzer, OLED.
  • Serial println: NOTIFY/DETAIL/STATUS lines.

Block sequence:

  1. Read TEMP, SOIL(%), RAIN.
  2. If soil is 40–70% and no rain, show “GARDEN GOOD” on OLED and celebrate with LED/buzzer.
  3. Else, print status and any alert (DRY_SOIL or RAIN).

MicroPython code:

import dhtx  # Import DHT11 library to read temperature
import machine  # Import machine for ADC, Pin, and hardware control
import time  # Import time to add tiny delays for effects
import oled128x64  # Import OLED driver for SSD1306 128x64

sensor = dhtx.DHT11(26)  # Create DHT11 on Pin 26 for temperature
adc32 = machine.ADC(machine.Pin(32))  # Create ADC on Pin 32 for soil sensor
adc32.atten(machine.ADC.ATTN_11DB)  # Configure ADC attenuation for full-scale readings
adc32.width(machine.ADC.WIDTH_12BIT)  # Set ADC resolution to 12-bit (0–4095)
rain = machine.Pin(35, machine.Pin.IN)  # Create raindrop digital input on Pin 35
led = machine.Pin(13, machine.Pin.OUT)  # Create LED output on Pin 13
buzzer = machine.Pin(23, machine.Pin.OUT)  # Create buzzer output on Pin 23
i2c = machine.SoftI2C(scl=machine.Pin(22), sda=machine.Pin(21), freq=100000)  # Setup I2C bus for OLED on pins 22/21
oled = oled128x64.OLED(i2c, address=0x3c, font_address=0x3A0000, types=0)  # Initialize OLED at address 0x3C
print("Microproject 5.12.5: Garden Guardian ready")  # Print project status

temp_c = sensor.temperature()  # Read temperature in Celsius from DHT11
raw = adc32.read()  # Read raw soil moisture value (0–4095) from ADC
soil_pct = int((4095 - raw) * 100 / 4095)  # Convert raw soil value to percent (higher number = wetter)
rain_state = rain.value()  # Read raindrop state (1=wet trigger, 0=dry)
print("TEMP:", temp_c)  # Print temperature for app
print("SOIL(%):", soil_pct)  # Print soil percentage for app
print("RAIN:", rain_state)  # Print rain state for app

oled.clear()  # Clear OLED screen before showing information
oled.shows('Garden Guardian', x=0, y=0, size=1, space=0, center=False)  # Show project title
oled.shows('T:'+str(temp_c)+' S:'+str(soil_pct)+' R:'+str(rain_state), x=0, y=12, size=1, space=0, center=False)  # Show values
oled.show()  # Refresh OLED to display text

if (soil_pct >= 40) and (soil_pct <= 70) and (rain_state == 0):  # Check ideal garden condition
    print("STATUS:GARDEN_GOOD")  # Print positive status
    print("NOTIFY:GARDEN_GOOD")  # Print notification tag for app
    print("DETAIL:TEMP="+str(temp_c)+",SOIL="+str(soil_pct)+",RAIN="+str(rain_state))  # Print details line
    for i in range(3):  # Run a short celebration pattern three times
        led.value(1)  # Turn LED ON to celebrate
        buzzer.value(1)  # Turn buzzer ON for a quick beep
        time.sleep(0.1)  # Hold state for 100 ms
        led.value(0)  # Turn LED OFF to blink
        buzzer.value(0)  # Turn buzzer OFF to stop beep
        time.sleep(0.1)  # Pause for 100 ms before next blink
    oled.shows('GARDEN GOOD!', x=0, y=28, size=2, space=0, center=False)  # Show big positive message
    oled.show()  # Refresh OLED to display celebration text
else:  # If conditions are not ideal
    if soil_pct < 30:  # If soil is too dry
        print("ALERT:DRY_SOIL")  # Print dry soil alert
        oled.shows('DRY SOIL', x=0, y=28, size=2, space=0, center=False)  # Show alert text
    if rain_state == 1:  # If rain is detected
        print("ALERT:RAIN")  # Print rain alert
        oled.shows('RAIN DETECTED', x=0, y=48, size=1, space=0, center=False)  # Show rain alert text
    oled.show()  # Refresh OLED to display alerts

Reflection: You combined reading, deciding, and celebrating—this is your robot’s “personality.”
Challenge:

  • Easy: Add “PLACE:GARDEN_A” in the details print.
  • Harder: Add a “MOOD” variable (HAPPY/ALERT) and change LED/buzzer patterns by mood.

Main project – Full integration showcase

Blocks steps (with glossary)

  • Inputs: DHT11, soil, rain, sound on official pins.
  • Outputs: Motors, LED, buzzer, OLED.
  • App control: BLE callback accepts MODE and TASK commands.
  • Routines: Named sequences like MORNING/EVENING.
  • Guardian mode: “Garden Guardian” celebration when conditions are ideal.

Block sequence:

  1. Setup all hardware (sensors, outputs, OLED, BLE).
  2. Define helper functions (motors, tasks, routines, guardian).
  3. Handle app commands and run the right feature.
  4. Print status and keep OLED updated.
  5. Keep loop responsive with short delays.

MicroPython code (mirroring blocks)

# Project 5.12 – Integration N1 to N5 (Full showcase)

import dhtx  # Import DHT11 temperature sensor library
import machine  # Import machine for ADC, Pin, and hardware control
import oled128x64  # Import OLED driver for SSD1306 128x64
import ble_central  # Import BLE central role
import ble_peripheral  # Import BLE peripheral role
import ble_handle  # Import BLE handler for receive callbacks
import time  # Import time for delays and timing windows

# Hardware setup: sensors
sensor = dhtx.DHT11(26)  # DHT11 sensor on Pin 26
adc32 = machine.ADC(machine.Pin(32))  # Soil sensor ADC on Pin 32
adc32.atten(machine.ADC.ATTN_11DB)  # ADC attenuation configured for full scale
adc32.width(machine.ADC.WIDTH_12BIT)  # ADC resolution set to 12-bit (0–4095)
rain = machine.Pin(35, machine.Pin.IN)  # Raindrop DO input on Pin 35
sound = machine.Pin(34, machine.Pin.IN)  # Sound sensor DO input on Pin 34
print("Sensors: DHT11=26, Soil=32, Rain=35, Sound=34")  # Confirm sensor pins

# Hardware setup: outputs
left_in1 = machine.Pin(18, machine.Pin.OUT)  # Left motor IN1
left_in2 = machine.Pin(19, machine.Pin.OUT)  # Left motor IN2
right_in3 = machine.Pin(5, machine.Pin.OUT)  # Right motor IN3
right_in4 = machine.Pin(23, machine.Pin.OUT)  # Right motor IN4
led = machine.Pin(13, machine.Pin.OUT)  # LED output
buzzer = machine.Pin(23, machine.Pin.OUT)  # Buzzer output (shared pin used carefully)
print("Outputs: Motors=18/19/5/23, LED=13, Buzzer=23")  # Confirm output pins

# OLED setup
i2c = machine.SoftI2C(scl=machine.Pin(22), sda=machine.Pin(21), freq=100000)  # I2C bus on pins 22/21
oled = oled128x64.OLED(i2c, address=0x3c, font_address=0x3A0000, types=0)  # OLED initialized at 0x3C
print("OLED ready at 0x3C")  # Confirm OLED setup

# BLE setup
ble_c = ble_central.BLESimpleCentral()  # Create BLE central object
ble_p = ble_peripheral.BLESimplePeripheral('Clu-Bots')  # Create BLE peripheral named 'Clu-Bots'
ble_c.scan()  # Start scanning for BLE peripheral
ble_c.connect(name='Clu-Bots')  # Connect to BLE peripheral by name
handle = ble_handle.Handle()  # Create BLE handler for callbacks
print("BLE connected to Clu-Bots")  # Confirm BLE connection

# Helpers: motors
def motors_stop():  # Stop both motors
    left_in1.value(0)  # Left IN1 LOW
    left_in2.value(0)  # Left IN2 LOW
    right_in3.value(0)  # Right IN3 LOW
    right_in4.value(0)  # Right IN4 LOW
    print("MOTORS:STOP")  # Print stop status

def motors_forward():  # Move forward
    left_in1.value(1)  # Left IN1 HIGH
    left_in2.value(0)  # Left IN2 LOW
    right_in3.value(1)  # Right IN3 HIGH
    right_in4.value(0)  # Right IN4 LOW
    print("MOTORS:FWD")  # Print forward status

# Helpers: routines
routines = {  # Define named routine steps
    "MORNING": ["ANNOUNCE:Good morning", "WATCH", "BRING"],  # Morning routine
    "EVENING": ["ANNOUNCE:Good evening", "WATCH", "STOP"]  # Evening routine
}
print("Routines:", list(routines.keys()))  # Print routine names

def run_routine(name):  # Execute a named routine
    print("ROUTINE:START", name)  # Announce start
    steps = routines.get(name, [])  # Get steps or empty
    for step in steps:  # Loop steps
        print("ROUTINE:STEP", step)  # Print current step
        execute(step)  # Execute command
    print("ROUTINE:DONE", name)  # Announce completion

# Helpers: tasks and announce
def task_bring():  # BRING task (forward 1 s)
    print("TASK:BRING_START")  # Announce start
    oled.clear()  # Clear OLED
    oled.shows('BRING...', x=0, y=20, size=1, space=0, center=False)  # Show task text
    oled.show()  # Refresh OLED
    motors_forward()  # Move forward
    time.sleep(1.0)  # Drive for 1 second
    motors_stop()  # Stop movement
    oled.shows('BRING DONE', x=0, y=35, size=1, space=0, center=False)  # Show completed text
    oled.show()  # Refresh OLED
    print("TASK:BRING_DONE")  # Announce completion

def task_watch():  # WATCH task (simulate scan)
    print("TASK:WATCH_START")  # Announce start
    oled.clear()  # Clear OLED
    oled.shows('WATCH...', x=0, y=20, size=1, space=0, center=False)  # Show task text
    oled.show()  # Refresh OLED
    time.sleep(0.5)  # Simulate scanning delay
    print("STATUS:AREA_CLEAR")  # Print status
    oled.shows('AREA CLEAR', x=0, y=35, size=1, space=0, center=False)  # Show status
    oled.show()  # Refresh OLED
    print("TASK:WATCH_DONE")  # Announce completion

def task_announce(text="HELLO"):  # ANNOUNCE task (print message)
    print("TASK:ANNOUNCE", text)  # Announce message
    oled.clear()  # Clear OLED
    oled.shows('ANNOUNCE:', x=0, y=10, size=1, space=0, center=False)  # Label text
    oled.shows(text, x=0, y=25, size=1, space=0, center=False)  # Show message
    oled.show()  # Refresh OLED

# Garden Guardian celebration helper
def garden_guardian():  # Celebrate good garden conditions
    temp_c = sensor.temperature()  # Read temperature
    raw = adc32.read()  # Read soil raw value
    soil_pct = int((4095 - raw) * 100 / 4095)  # Map to percent
    rain_state = rain.value()  # Read rain state
    print("TEMP:", temp_c)  # Print temperature
    print("SOIL(%):", soil_pct)  # Print soil percent
    print("RAIN:", rain_state)  # Print rain state
    oled.clear()  # Clear OLED
    oled.shows('Garden Guardian', x=0, y=0, size=1, space=0, center=False)  # Title
    oled.shows('T:'+str(temp_c)+' S:'+str(soil_pct)+' R:'+str(rain_state), x=0, y=12, size=1, space=0, center=False)  # Values
    oled.show()  # Refresh OLED
    if (soil_pct >= 40) and (soil_pct <= 70) and (rain_state == 0):  # Ideal condition
        print("STATUS:GARDEN_GOOD")  # Print good status
        print("NOTIFY:GARDEN_GOOD")  # Notification tag
        print("DETAIL:TEMP="+str(temp_c)+",SOIL="+str(soil_pct)+",RAIN="+str(rain_state))  # Details
        for i in range(3):  # Celebration pattern
            led.value(1)  # LED ON
            buzzer.value(1)  # Buzzer ON
            time.sleep(0.1)  # Short on time
            led.value(0)  # LED OFF
            buzzer.value(0)  # Buzzer OFF
            time.sleep(0.1)  # Short off time
        oled.shows('GARDEN GOOD!', x=0, y=28, size=2, space=0, center=False)  # Big message
        oled.show()  # Refresh OLED
    else:  # Not ideal
        if soil_pct < 30:  # Dry soil case
            print("ALERT:DRY_SOIL")  # Dry alert
            oled.shows('DRY SOIL', x=0, y=28, size=2, space=0, center=False)  # OLED alert
        if rain_state == 1:  # Rain case
            print("ALERT:RAIN")  # Rain alert
            oled.shows('RAIN DETECTED', x=0, y=48, size=1, space=0, center=False)  # OLED alert
        oled.show()  # Refresh OLED

# Executor: run commands from app
def execute(cmd):  # Execute a command string
    print("EXEC:", cmd)  # Print command for app
    if cmd == "BRING":  # If BRING requested
        task_bring()  # Run BRING task
    elif cmd == "WATCH":  # If WATCH requested
        task_watch()  # Run WATCH task
    elif cmd.startswith("ANNOUNCE:"):  # If ANNOUNCE with text
        task_announce(cmd.split(":", 1)[1])  # Run ANNOUNCE with message
    elif cmd == "GUARDIAN":  # If Garden Guardian requested
        garden_guardian()  # Run celebration/status
    elif cmd == "STOP":  # If STOP requested
        motors_stop()  # Stop motors
    else:  # Unknown command
        print("CMD:UNKNOWN", cmd)  # Report unknown

# BLE callback: handle app commands
def handle_method(key1, key2, key3, keyx):  # BLE receive callback for app
    msg = str(key1)  # Convert payload to text
    print("APP->R32:", msg)  # Print command from app
    if msg.startswith("ROUTINE:"):  # If routine call
        run_routine(msg.split(":", 1)[1])  # Run routine by name
    else:  # Otherwise treat as task
        execute(msg)  # Execute command

handle.recv(handle_method)  # Attach BLE receive callback

# Main loop: periodic guardian check to keep system lively
while True:  # Continuous loop
    print("HB:RUNNING")  # Heartbeat message
    time.sleep(3)  # Small delay to avoid spamming

External explanation

  • What it teaches: Integration means connecting everything—sensors, actuators, app, routines—and giving them simple rules to work together.
  • Why it works: Clear helper functions and short, labeled messages keep the system understandable; BLE callbacks and tiny delays keep it responsive; OLED makes the state visible at a glance.
  • Key concept: “Read → decide → act → show.”

Story time

Your robot wakes up like a tiny butler. It checks the garden, flashes a happy light when conditions are perfect, announces the plan, and runs your routine. It’s not just parts—it’s a personality.


Debugging (2)

Debugging 5.12.1 – Complexity in integration

Problem: Features step on each other (e.g., motors and buzzer share timing and feel messy).
Clues: Messages overlap, OLED flickers, actions interrupt.
Broken code:

execute("BRING")  # Starts motors
execute("GUARDIAN")  # Immediately runs celebration and prints over BRING

Fixed code:

def safe_run(seq):  # Helper to run steps in order
    for step in seq:  # Iterate through steps
        execute(step)  # Execute current step
        time.sleep(0.3)  # Small gap to avoid overlap
# Example: safe_run(["BRING", "GUARDIAN"])  # Run BRING then GUARDIAN cleanly

Why it works: A tiny gap between actions reduces clashes and makes outputs readable.
Avoid next time: Sequence multi‑step actions with short delays and clear prints.

Debugging 5.12.2 – Cascading failures

Problem: One error triggers many more (e.g., bad sensor read breaks multiple checks).
Clues: Soil percent prints strange values; alerts all trigger at once.
Broken code:

soil_pct = int((4095 - raw) * 100 / 4095)  # No guard for invalid raw
print("SOIL(%):", soil_pct)  # Might print negative or >100 if raw bad

Fixed code:

raw = adc32.read()  # Read raw value
raw = 0 if raw < 0 else (4095 if raw > 4095 else raw)  # Clamp to valid range
soil_pct = int((4095 - raw) * 100 / 4095)  # Map to 0–100 percent
print("SOIL(%):", soil_pct)  # Print clamped percent

Why it works: Clamping keeps values in range, preventing chain reactions of wrong alerts.
Avoid next time: Add simple guards and sanity checks around sensor reads.


Final checklist

  • Garden Guardian celebration shows when soil is 40–70% and rain=0
  • BRING/WATCH/ANNOUNCE run cleanly with prints and OLED updates
  • BLE commands execute immediately, including ROUTINE and GUARDIAN
  • Heartbeat prints keep the app UI in sync without spam
  • Sensor value prints are in range and understandable

Extras

  • 🧠 Student tip: Add “PROFILE:QUIET” to use LED only (no buzzer) during quiet hours.
  • đŸ§‘â€đŸ« Instructor tip: Ask students to draw their integration map (inputs → decisions → actions → displays) before coding; it prevents overlaps.
  • 📖 Glossary:
    • Integration: Combining multiple features so they cooperate.
    • Heartbeat: A periodic print that shows the system is alive.
    • Clamping: Limiting a value to a safe range to prevent bad behavior.
  • 💡 Mini tips:
    • Keep prints short and labeled (STATUS, ALERT, NOTIFY, DETAIL).
    • Use tiny delays between actions to avoid flicker.
    • Reuse helpers and routines so the code stays small and clear.
On this page