JB logo

Command Palette

Search for a command to run...

yOUTUBE
back to the course
Projects Track · build things people pay for

The Robot Whisperer: Projects

Four real automations — each with a full wiring diagram, parts list, and firmware — followed by the module that turns them into a business. Everything here assumes you've been through the course up to at least Module 8.

Project P1Access control

RFID card door lock

Goal: upgrade the course's smart lock so it opens with a tap of a card — the same experience as a hotel door — with an allow-list you control.

🧒 Picture this

Each RFID card has a tiny chip and antenna inside — no battery. When it comes near the reader, the reader's radio field powers the card through the air, and the card wakes up just long enough to shout its serial number (the UID). Your job: catch that number and check it against a guest list.

Parts

PartWhy you need it
RC522 reader + cards/fobsUsually sold as a kit with one card and one keyring fob. 3.3V only!
SG90 servo (or solenoid + driver)The bolt — same one from Module 6.
Active buzzerFeedback: one happy beep = open, three angry beeps = refused.
ESP32 + breadboard + wiresYour existing kit.

Wiring

wiring — RFID card lock (RC522 + servo + buzzer)
ESP32 DevKit3V3GNDGPIO5GPIO18GPIO19GPIO22GPIO23VIN 5VGPIO13GPIO15USBRC522 RFID reader — talks SPI, powered from 3.3V ONLYRC5223.3VRSTGNDIRQMISOMOSISCKSDA3V3 → RC522 3.3VGPIO 22 → RSTGND → RC522 GNDGPIO 19 → MISOGPIO 23 → MOSIGPIO 18 → SCKGPIO 5 → SDAVIN 5V → servo redGND trunk → servo brownGPIO 13 → servo orangeSG90 servo — brown=GND, red=5V, orange=signalSG90GPIO 15 → buzzer +GND → buzzer −Active buzzer — beeps when its pin goes HIGHbuzzer
3.3V / 5V powerGNDSDA (chip select)SCK (clock)MOSIMISORST / servo signal
ESP32 pinConnects toWire
3V3RC522 3.3V — NEVER 5V, it kills the readerred
GNDRC522 GND + servo BROWN + buzzer −black
GPIO 5RC522 SDA (chip select)green
GPIO 18RC522 SCK (clock)blue
GPIO 23RC522 MOSIyellow
GPIO 19RC522 MISOpurple
GPIO 22RC522 RSTorange
VIN (5V)servo RED wirered
GPIO 13servo ORANGE (signal)orange
GPIO 15buzzer +green

Seven wires to the reader looks scary, but it's just SPI (one clock, two data lines, one select) plus power. IRQ stays unconnected. The reader is 3.3V-only — wiring its VCC to 5V is the #1 way people kill an RC522.

Step 1 — enrol cards: read their UIDs

Install the MFRC522 library (PlatformIO: miguelbalboa/MFRC522), flash this, and tap every card you want to allow:

enrol.cpp
#include <SPI.h>
#include <MFRC522.h>

#define SS_PIN  5    // the reader's SDA pin
#define RST_PIN 22

MFRC522 rfid(SS_PIN, RST_PIN);

void setup() {
    Serial.begin(115200);
    SPI.begin();       // SCK=18, MISO=19, MOSI=23 — ESP32 defaults
    rfid.PCD_Init();
    Serial.println("Tap a card...");
}

void loop() {
    if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) return;

    Serial.print("Card UID:");
    for (byte i = 0; i < rfid.uid.size; i++) {
        Serial.printf(" %02X", rfid.uid.uidByte[i]);   // hex! (see the Interlude)
    }
    Serial.println();
    rfid.PICC_HaltA();   // stop talking to this card
}
Serial Monitor — tap each card
Tap a card...
Card UID: 9C 7A 12 4B
Card UID: 04 D1 33 A2

Step 2 — the lock itself

Copy those UIDs into the allow-list and flash the real firmware:

rfid_lock.cpp
#include <SPI.h>
#include <MFRC522.h>
#include <ESP32Servo.h>

MFRC522 rfid(5, 22);
Servo bolt;
const int BUZZER = 15;

// The allow-list: UIDs you copied from the enrol sketch.
byte allowed[][4] = {
    {0x9C, 0x7A, 0x12, 0x4B},   // JB's card
    {0x04, 0xD1, 0x33, 0xA2},   // spare card
};

bool isAllowed(byte* uid, byte size) {
    for (auto& card : allowed) {
        if (size == 4 && memcmp(uid, card, 4) == 0) return true;
    }
    return false;
}

void setup() {
    Serial.begin(115200);
    SPI.begin();
    rfid.PCD_Init();
    bolt.attach(13);
    bolt.write(0);                 // start LOCKED
    pinMode(BUZZER, OUTPUT);
}

void loop() {
    if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) return;

    if (isAllowed(rfid.uid.uidByte, rfid.uid.size)) {
        digitalWrite(BUZZER, HIGH); delay(80); digitalWrite(BUZZER, LOW);
        bolt.write(90);            // open
        delay(3000);               // (make this non-blocking — Module 7!)
        bolt.write(0);             // auto-lock
    } else {
        for (int i = 0; i < 3; i++) {          // three angry beeps
            digitalWrite(BUZZER, HIGH); delay(60);
            digitalWrite(BUZZER, LOW);  delay(60);
        }
    }
    rfid.PICC_HaltA();
}
What you should see — in the REAL WORLD
Tap an allowed card  → happy beep, bolt opens, re-locks after 3s.
Tap a stranger's card → three angry beeps, bolt doesn't move.
Honest security note: a UID can be cloned with a $10 gadget, so UID-only checking is fine for an office cabinet or a demo — not for anything serious. Real products do a cryptographic challenge–response with the card's secret keys (look up MIFARE DESFire), and log every entry to the backend (Module 11). The Module 9 mindset applies to cards too.
Upgrade path: store the allow-list in flash (Preferences library) instead of hard-coding it, and add an "enrol mode" — hold a master card 5 seconds, then every card tapped in the next 30 seconds gets added. Now you can hand the client new cards without reflashing.

✅ Project 1 — ship it

Two cards enrolled, one refused stranger card, auto-relock, and beeps that make the state obvious without looking. Bonus: publish every open/refuse event over MQTT (Module 8) so the dashboard shows who came in and when.
back to top of module

Project P2Signage

Signpost with lights (WS2812B)

Goal: drive an addressable LED strip — every LED individually controllable — to build shop signs that animate, glow in brand colours, and switch themselves on at dusk.

🧒 Picture this

A normal LED strip is one big light: on or off, all together. A WS2812B strip is different — every single LED has its own tiny chip, like a string of children holding hands where you whisper a message to the first one and each passes the rest along. One data wire in, and you can tell LED #37 to be cyan while #38 stays dark. That is how signs animate.

Parts

PartWhy you need it
WS2812B strip (60 LEDs/m)Start with 1m. IP65 (rubber-coated) for anything outdoors.
5V power supply, 4A+The strip's real food. 60 LEDs at full white ≈ 3.6A — USB cannot do this.
330Ω resistorIn the data line — protects the first LED's input.
1000µF capacitorAcross the strip's power input — absorbs the turn-on surge.
Do the power math before you buy. Each LED can draw ~60mA at full white. 60 LEDs × 60mA = 3.6A; a 5m sign strip is 300 LEDs = 18A. That's why the PSU — not the ESP32 — feeds the strip, why long runs need power injected every 2–3 metres from both ends, and why setBrightness() is also a power-budget tool, not just a looks tool.

Wiring

wiring — LED signpost (WS2812B strip + its own 5V supply)
ESP32 DevKitVIN 5VGNDGPIO16USB5V 4A PSU+mains in5VDINGNDWS2812B strip (60 LEDs/m)PSU + → strip 5V padPSU + → ESP32 VIN (powers the board too)PSU − → strip GND padPSU − → ESP32 GND (shared ground!)GPIO 16 → 330Ω330Ω → strip DINResistor 330Ω330Ω1000µF capacitor — smooths the inrush when the strip lights up1000µF
5V from the PSU (strip AND ESP32)GND — everything shares itGPIO 16 → 330Ω → DIN
ESP32 pinConnects toWire
PSU +strip 5V pad AND ESP32 VINred
PSU −strip GND pad AND ESP32 GNDblack
GPIO 16330Ω resistor → strip DINgreen

The strip eats amps, so it drinks straight from the PSU — the ESP32 only supplies the skinny data line. The 330Ω resistor protects the first LED's data pin; the 1000µF capacitor absorbs the power-on surge. Data flows ONE way: connect to the end marked DIN, not DOUT.

First light

Install Adafruit NeoPixel (PlatformIO: adafruit/Adafruit NeoPixel) and run the classic moving-dot test:

strip_test.cpp
#include <Adafruit_NeoPixel.h>

const int PIN   = 16;
const int COUNT = 60;
Adafruit_NeoPixel strip(COUNT, PIN, NEO_GRB + NEO_KHZ800);

void setup() {
    strip.begin();
    strip.setBrightness(80);   // 0-255. Start LOW — full white is blinding
    strip.show();              // everything off
}

void loop() {
    // A moving dot ("chase") in your brand colour.
    for (int i = 0; i < COUNT; i++) {
        strip.clear();
        strip.setPixelColor(i, strip.Color(0x22, 0xD3, 0xEE));  // hex RGB —
        strip.show();                                           // the Interlude pays off
        delay(30);
    }
}
What you should see — in the REAL WORLD
A single cyan dot runs down the strip, over and over.
If nothing lights: check DIN vs DOUT (data has a direction!)
and that the strip's GND is tied to the ESP32's GND.

Making it a product: the dusk-to-close schedule

A shop sign that someone must remember to switch on is a broken product. The ESP32 has Wi-Fi, so it can simply ask the internet what time it is (a protocol called NTP) and run itself:

sign_schedule.cpp
#include <WiFi.h>
#include "time.h"

void setup() {
    // ...connect Wi-Fi (Module 8)...
    // Ask an internet time server what time it is. UTC+3 = Kampala.
    configTime(3 * 3600, 0, "pool.ntp.org");
}

bool signShouldBeOn() {
    struct tm now;
    if (!getLocalTime(&now)) return true;    // clock not synced yet? stay on
    int minutes = now.tm_hour * 60 + now.tm_min;
    return minutes >= 18 * 60 + 30           // on from 18:30...
        && minutes <  23 * 60;               // ...off at 23:00
}

void loop() {
    if (signShouldBeOn()) {
        runAnimation();          // your chase/fade/scroll effect
    } else {
        strip.clear();
        strip.show();            // dark — saves power and bulb life
    }
}
Animations that sell: a slow colour-fade in the shop's brand colours, a "breathing" glow, and a chase for attention. Anything faster than that reads as cheap. Expose the choice + brightness over MQTT or the web endpoints from Project 3 and the client can change the mood from their phone — which, to them, is magic.

✅ Project 2 — ship it

A strip in brand colours that turns itself on at 18:30 and off at 23:00 with no human involved, surviving a power cut (it re-syncs time on boot). Bonus: a phone-adjustable brightness slider via a /brightness?level=120 endpoint.
back to top of module

Project P3Switching

Lights from a remote AND an app

Goal: switch a real lamp three ways — an IR remote from the sofa, a phone on the same network, and (via your Module 11 backend) from anywhere on earth.

🧒 Picture this

A relay is a light switch flipped by electricity instead of a finger. Your ESP32 gives a tiny 3.3V nudge on the safe side; inside, an electromagnet physically pulls a metal contact closed on the other side — you can hear the click — and that contact can carry mains power. The chip never touches the dangerous side. It just flips the switch.
Read this twice: mains electricity can kill. Build and test this entire project with a 12V LED bulb and a 12V supply first — the logic is identical and mistakes cost nothing. When it becomes a real install, the 230V side goes in an enclosure and is connected by a licensed electrician. In the business module this is a rule, not advice: you sell the brains, a professional wires the muscle.

Parts

PartWhy you need it
1-channel 5V relay moduleThe opto-isolated type. Rated 10A/250VAC — plenty for lamps.
VS1838B IR receiverThe little black 'eye' that reads remote controls.
Any IR remoteA spare TV remote works — we learn whatever codes it sends.
12V bulb + 12V supplyYour SAFE practice lamp before any mains work.

Wiring

wiring — remote + app lights (relay module, IR eye, and the mains zone)
ESP32 DevKitVIN 5VGNDGPIO26GPIO353V3USBRelay module — the chip side is safe; the screw side switches mainsSRD-05VDC10A 250VACVCCGNDINCOMNONC⚡ 230V MAINSelectrician territoryVIN 5V → relay VCCGND → relay GNDGPIO 26 → relay INmains LIVE → relay COMrelay NO → lamplamp → mains NEUTRALto mainsThe lamp being switchedIR OUT → GPIO 353V3 → IR VCCIR GND → GNDIR receiver — OUT, GND, VCC (3.3V)IR eyeAny IR remote works — we read whatever codes it sends
5V / 3.3V (safe side)GNDGPIO 26 → relay INIR OUT → GPIO 35230V LIVE — danger zone
ESP32 pinConnects toWire
VIN (5V)relay VCCred
GNDrelay GND + IR eye GNDblack
GPIO 26relay IN (HIGH = click on)yellow
GPIO 35IR receiver OUTpurple
3V3IR receiver VCCred
— mains —LIVE → COM, NO → lamp → NEUTRAL230V cable

Everything left of the dashed line is safe 3.3–5V. Everything inside it is 230V mains: the relay's COM/NO contacts sit in the lamp's LIVE wire like a switch. Practice with a 12V lamp first — and have a licensed electrician do the real mains connection.

Step 1 — learn your remote's codes

Install IRremote (PlatformIO: Armin Joachimsmeyer/IRremote). Every button on every remote sends a unique code — sniff them:

ir_read.cpp
#include <IRremote.hpp>

const int IR_PIN = 35;

void setup() {
    Serial.begin(115200);
    IrReceiver.begin(IR_PIN);
    Serial.println("Point any remote at the eye and press buttons...");
}

void loop() {
    if (IrReceiver.decode()) {
        // Every button sends its own code. Write them down.
        Serial.printf("code: 0x%02X\n", IrReceiver.decodedIRData.command);
        IrReceiver.resume();     // ready for the next press
    }
}
Serial Monitor — pressing buttons
Point any remote at the eye and press buttons...
code: 0x45
code: 0x46
code: 0x47

Step 2 — remote + app control in one loop

relay_lights.cpp
#include <WiFi.h>
#include <WebServer.h>
#include <IRremote.hpp>

const int RELAY = 26;
WebServer server(80);
bool lightOn = false;

void setLight(bool on) {
    lightOn = on;
    digitalWrite(RELAY, on ? HIGH : LOW);   // HIGH = relay clicks closed
}

void setup() {
    pinMode(RELAY, OUTPUT);
    // ...connect Wi-Fi (Module 8)...
    IrReceiver.begin(35);

    // Three tiny HTTP endpoints — your phone/app is the remote now.
    server.on("/on",  [] { setLight(true);  server.send(200, "text/plain", "ON");  });
    server.on("/off", [] { setLight(false); server.send(200, "text/plain", "OFF"); });
    server.on("/toggle", [] {
        setLight(!lightOn);
        server.send(200, "text/plain", lightOn ? "ON" : "OFF");
    });
    server.begin();
}

void loop() {
    server.handleClient();                   // app control
    if (IrReceiver.decode()) {               // remote control
        if (IrReceiver.decodedIRData.command == 0x45) {   // your power button
            setLight(!lightOn);
        }
        IrReceiver.resume();
    }
}
What you should see
Press the remote's power button → CLICK, lamp toggles.
From a phone on the same Wi-Fi:
  http://192.168.1.57/toggle   → lamp toggles, page says ON
This is where your web career plugs in. Those three endpoints are a REST API — point the Module 11 React dashboard at them and you have an app. For control from outside the house, don't port-forward; publish state over MQTT (Module 8) and let your Go backend relay commands, exactly like the smart lock. One backend, many devices — that's the product.

✅ Project 3 — ship it

One lamp, three control paths: IR remote, phone browser, and an MQTT message from your backend. All three stay in sync (the IR toggle shows up in the app). Do it on 12V — then price what an electrician charges to make it 230V-real.
back to top of module

Project P4Vision

Camera projects (ESP32-CAM)

Goal: a $7 board that takes photos and streams video — snap whoever presses the doorbell, send it to your backend, and pair it with the smart lock.

🧒 Picture this

The ESP32-CAM is the same chip you've been using, on a board with a camera and a microSD slot — for about the price of lunch. The catch: to fit the camera on, they threw away the USB port. So you flash it through a USB-serial (FTDI) adapter — four wires and one jumper. That flashing ritual is 90% of what beginners struggle with, so we make it explicit.

Parts

PartWhy you need it
ESP32-CAM (AI-Thinker) + OV2640Usually sold together. Get the antenna version for range.
FTDI / USB-serial adapter3.3V/5V jumper type. This is your programming cable.
Solid 5V supplyThe camera browns out weak USB ports — a proper 5V/2A source saves hours.
microSD card (optional)For local snapshots without a server.

The flashing ritual

Set the FTDI's little jumper to 5V (it selects the voltage on its 5V pin), and if upload fails with "Failed to connect", hold RST while the upload starts, releasing when you see Connecting...
wiring — flashing the ESP32-CAM through an FTDI adapter
ESP32-CAM (AI-Thinker) — no USB port, flashed through an FTDI adaptermicroSDESP32-CAM5VGNDIO12IO13U0RU0TGNDIO0FTDI USB-serial adapter — set its jumper to 5VFTDIUSB-serial5VGNDTXRX→ your laptopFTDI 5V → cam 5VFTDI GND → cam GNDFTDI TX → cam U0R (crossed)FTDI RX → cam U0T (crossed)IO0 → GND: flash mode — remove to run← flash jumper
5VGNDFTDI TX → cam U0RFTDI RX → cam U0TIO0 → GND (flash mode)
ESP32 pinConnects toWire
cam 5VFTDI 5V (set the FTDI jumper to 5V)red
cam GNDFTDI GNDblack
cam U0R (receive)FTDI TX (transmit) — crossed!green
cam U0T (transmit)FTDI RX (receive) — crossed!yellow
cam IO0cam GND — ONLY while flashingorange jumper

The ESP32-CAM has no USB port, so an FTDI adapter is its cable to your laptop. TX and RX cross (my transmit is your receive). The IO0→GND jumper puts the chip in flash mode: fit it, press reset, upload — then REMOVE it and press reset again to run.

  1. Wire it exactly as above — TX/RX crossed, IO0 jumpered to GND.
  2. Press the board's RST button. It boots into flash mode.
  3. Upload from PlatformIO (board: esp32cam).
  4. Remove the IO0 jumper, press RST again — now it runs your code.

First photo

cam_snap.cpp
#include "esp_camera.h"

void setup() {
    Serial.begin(115200);

    camera_config_t cfg = {};
    cfg.pixel_format = PIXFORMAT_JPEG;
    cfg.frame_size   = FRAMESIZE_SVGA;   // 800x600 — good speed/size balance
    cfg.jpeg_quality = 12;               // lower number = better quality
    cfg.fb_count     = 1;
    // ...plus ~16 pin assignments for the AI-Thinker board.
    // Copy that block verbatim from the CameraWebServer example —
    // every ESP32-CAM project uses the exact same lines.

    if (esp_camera_init(&cfg) != ESP_OK) {
        Serial.println("Camera init failed");
        return;
    }

    camera_fb_t* fb = esp_camera_fb_get();      // take one photo
    Serial.printf("Captured %u bytes of JPEG\n", fb->len);
    esp_camera_fb_return(fb);                   // ALWAYS hand the buffer back
}

void loop() {}
Serial Monitor
Captured 24816 bytes of JPEG
If it reboots in a loop printing "brownout detected": the camera's power spike is sagging your 5V line. It's almost never your code — use a shorter/fatter USB cable or a real 5V/2A supply. This single tip is worth the price of the module.

Send it somewhere useful

A photo on a chip helps nobody. POST it to the Go backend from Module 11 — which can store it, timestamp it, and push it to the React dashboard:

cam_upload.cpp
#include <HTTPClient.h>

// Send a captured frame to your own backend (the Go server from Module 11).
void sendPhoto(camera_fb_t* fb) {
    HTTPClient http;
    http.begin("http://your-server.com/api/doorbell");
    http.addHeader("Content-Type", "image/jpeg");
    int code = http.POST(fb->buf, fb->len);     // raw JPEG bytes, no base64
    Serial.printf("Upload: HTTP %d\n", code);
    http.end();
}
Serial Monitor
Upload: HTTP 200
Live streaming for free: the ESP32 Arduino core ships a CameraWebServer example that serves live MJPEG video on the camera's IP — flash it, open the address, and you have a viewfinder for aiming the camera during installs. For the doorbell product: wire the doorbell button to a GPIO, snap on press, upload, and let the backend WhatsApp/notify the owner — "someone is at the gate" with a face attached. Pair it with Project 1 and the dashboard shows who badged in.

✅ Project 4 — ship it

Doorbell press → photo lands on your server → dashboard (or WhatsApp) shows it within seconds. Bonus: a "live view" button using the streaming example. You now have every ingredient of a commercial video doorbell.
back to top of module

Module BIZThe business

From projects to an automation business

Goal: package the four projects as services, price them sanely, source parts without pain, and build the recurring revenue that makes this a business instead of a hobby.

🧒 Picture this

Nobody pays for "an ESP32 with a relay." They pay for "my shop sign turns itself on every evening" and "I can see who's at my gate from my phone." The hardware is your cost; the outcome is your product. Everything in this module follows from that one sentence.

The services catalogue

Each project you built maps to a service you can quote tomorrow. Clients buy from a menu, not from a parts list:

ServiceWhat you installYou built it inWho buys it
Smart access controlCard/fob door locks, entry logs, per-person access, auto-lockProject 1 + the capstone lockOffices, apartments & rentals, SACCOs, clinics, schools
LED signage & ambienceAnimated shop signs, brand-colour lighting, dusk-to-close schedulingProject 2Shops, bars & restaurants, salons, churches, events
Remote & app controlLights, pumps, gates and appliances switched from a remote, phone, or timerProject 3Homes, farms (irrigation, poultry heating), landlords
Camera & monitoringPhoto doorbells, gate cameras, 'who badged in' snapshotsProject 4 (+ Project 1)Gated homes, shops, warehouses
Dashboards & integrationOne web app showing every device — history, alerts, controlModule 11 (your web skills)Every client above — this is what makes you look 10× bigger

Pricing: charge for the outcome, not the parts

The parts for a card-lock install cost roughly $15–25. If you price at parts-plus-a-little, you've invented a badly paid hardware shop. Price the install instead:

  • Site survey first, always. A short paid (or credited) visit: measure, photograph, check power and Wi-Fi coverage. It filters non-serious clients and kills nasty surprises.
  • Install price ≈ 4–6× parts cost as a starting heuristic — that's parts + your labour + your drive time + a margin for the one component that always arrives dead.
  • Quote three tiers. Basic (the thing works), Standard (+ app control), Premium (+ dashboard, history, alerts). Most clients pick the middle one — so design the middle one to be the one you want to sell.
  • The dashboard is your differentiator. Any electronics shop can wire a relay. Almost none can hand the client a clean web app with history and alerts. That's your Module 11 superpower — price it like the moat it is.

Recurring revenue: the maintenance contract

One-off installs feed you this month. Contracts feed you every month:

  • A monthly fee covers: remote monitoring (is the device online?), signed OTA updates (Module 9), card enrolment/removal, and a same-week fix if something dies.
  • Your MQTT backend already knows when a device goes offline — turn that into a WhatsApp alert to yourself and you're running a mini-NOC for a fleet of clients from one laptop.
  • Rule of thumb: aim for contract revenue to pass install revenue within a year. Ten clients at a modest monthly fee is real money in Kampala — and it compounds.

Sourcing parts without pain

  • Order 5–10× of everything cheap (ESP32s, relays, RC522s, sensors). Shipping dominates cost, boards fail QC, and a client demo you can't do because your only relay died is an expensive lesson.
  • AliExpress lead time is 3–6 weeks to East Africa — order for the pipeline, not the current job. Keep a shortlist of local electronics shops for emergencies and pay their premium gladly.
  • Test every board the day it arrives (a one-minute blink flash). Discovering a dead board at install time costs you a client; discovering it at your desk costs you nothing.
  • Build a spares kit that travels with you: one of everything, plus wire, tape, zip ties, a multimeter, and a powered USB hub.

The demo case: your best salesperson

🧒 Picture this

Nobody in a shop wants to imagine what "IoT automation" means. So don't make them imagine: build a suitcase demo — a board with a working card lock, a strip of animated LEDs, a relay clicking a 12V bulb, and your dashboard on a tablet. Tap the card, the bolt opens. Tap the phone, the light obeys. Deals close when the client's own finger makes something physical move.
  • Film every install (with permission) — 30-second WhatsApp-ready clips.
  • Give your first 2–3 installs at a discount in exchange for being reference sites you can bring prospects to.
  • A WhatsApp Business catalogue of your services beats a website for local trade — but the dashboard demo link makes you look enterprise.

The safety & legal line (non-negotiable)

You sell the brains. A licensed electrician wires the mains. Everything you build lives on the 5V side of a relay or PSU; the 230V side — breaker, enclosure, cabling — is installed and signed off by a professional you partner with (and whose fee is in your quote). One burned shop erases a business; one good electrician partner scales it. For door locks, put the fail-safe/fail-secure decision (Module 10) in writing and have the client sign it.
  • Hand over a one-page document per install: what was installed, how to use it, the fail-state decision, and what the maintenance contract covers. Module 10's documentation habit, now with an invoice.
  • Offer a defined warranty (e.g. 6 months parts + labour). Vague promises create endless free callouts; a written boundary creates contract upsells.

✅ Business milestone — do this before the next install

Write your one-page services catalogue from the table above. Price all three tiers for ONE service you can deliver this month. Build the suitcase demo from projects you already have on your desk. Then book two demos with real shops. Code you've written + a demo case + a price list — that is a business, and you already have all three.
back to top of module
Every project here is a service on your future price list. Build them on your desk first, film them working, and keep the demo case ready — the first client is closer than you think.