Capsicum

🫑 Retrofit a wired doorbell to add WiFi and make it connected 🔔

power 16340
wireless WiFi
mcu ESP32-C3
ongoing since Oct 2023

Demo

esp32c3 push button buzzer bell medium

Press the doorbell to sound the bell and send a notification if the current time is appropriate


Details

This example shows how to use the ESP32-C3 dev board to do the following:

  1. Press the button to wakeup from deep sleep
  2. Then, check the current time check with NTP to see if it is appropriate to sound the bell
  3. Then, sound the bell if the time is fine
  4. Then, send a HTTPS POST to Zapier
  5. Then go back to sleep again until the bell is pressed

Serial console

Serial output from the firmware.

Schematic

Wire up the hardware accordingly

 setup

Code

Download code
#ifdef PRODUCTION
  #define DEBUG DBG_NONE
#else
  #define DEBUG DBG_VERBOSE
#endif
// Change the debug level accordingly:
// DBG_NONE, DBG_ERROR, DBG_WARNING,
// DBG_INFO (default), DBG_DEBUG, and DBG_VERBOSE

#include "Arduino_DebugUtils.h"
#include "src/bell/bell.h"
#include "src/ledController/ledController.h"
#include "src/sleepManager/sleepManager.h"
#include "src/wifiConnector/wifiConnector.h"
#include "src/timeManager/timeManager.h"
#include "src/webhookClient/webhookClient.h"
#include "src/batteryLevel/batteryLevel.h"
#include "Secret.h"

// Timeout for the bell to ring
const int bellTimeout = 30000;  // 30 seconds

// GMT+8 (8 hours * 60 minutes * 60 seconds)
const long timeZoneOffset = 28800;
const int startTime = 9;  // 9am or 0900h
const int endTime = 21;  // 9pm or 2100h

#define BELL_PIN 7
#define LED 3
#define WAKEUP_INTERRUPT_PIN 4
#define BATTERY_ENABLE_PIN 6
#define BATTERY_MEASUREMENT_PIN 5

// Connect this pin to HIGH if you want to stay awake
// E.g. for uploading firmware
#define SLEEP_CHECK_PIN 2

Bell bell;
LEDController ledController(LED);
SleepManager sleepManager(WAKEUP_INTERRUPT_PIN, SLEEP_CHECK_PIN);
WiFiConnector wifiConnector(ssid, pass);
TimeManager timeManager(timeZoneOffset, startTime, endTime);
BatteryLevel batteryLevel(BATTERY_ENABLE_PIN, BATTERY_MEASUREMENT_PIN);
WebhookClient webhookClient;

void setup() {
  if (DEBUG != DBG_NONE) {
    Serial.begin(115200);
    while (!Serial) { }
  }

  Debug.setDebugLevel(DEBUG);
  Debug.timestampOn();

  wifiConnector.connect();
  if (wifiConnector.isConnected()) {
    handleConnectedWiFi();
  } else {
    initializeAndRingBell();
  }

  delay(bellTimeout);  // Timeout for the doorbell

  #ifndef PRODUCTION
    ledController.init();
  #endif
}

void loop() {
  #ifndef PRODUCTION
    ledController.blink(1);
  #endif

  if (sleepManager.shouldGoToSleep()) {
    DEBUG_INFO("Going to sleep");
    sleepManager.sleep();
  }

  DEBUG_INFO("Staying awake");
}

void handleConnectedWiFi() {
  DEBUG_INFO("Connected to WiFi SSID ");
  DEBUG_INFO(wifiConnector.getSSID());

  ringBellIfNeeded();

  // Send to Zapier in production environment
  // 100/month Zapier limit
  #ifdef PRODUCTION
    sendWebhookToZapier();
  #endif

  wifiConnector.disconnect();
}

void ringBellIfNeeded() {
  DEBUG_DEBUG("Initializing time manager...");
  timeManager.init();
  DEBUG_DEBUG("Time manager initialized.");

  if (timeManager.isCurrentTimeInRange()) {
    initializeAndRingBell();
  } else {
  DEBUG_DEBUG("Not ringing the bell! The time is not right.");
  }
}

void sendWebhookToZapier() {
  WebhookClientConfig config = prepareWebhookConfig();

  DEBUG_INFO("Sending webhook to Zapier");
  if (!webhookClient.sendWebhook(&config)) {
    DEBUG_ERROR("Unsuccessful sending to Zapier");
  } else {
    DEBUG_INFO("Successful in sending the webhook to Zapier");
  }
}

void initializeAndRingBell() {
  DEBUG_DEBUG("Initializing bell...");
  bell.init(BELL_PIN);
  DEBUG_DEBUG("Bell initialized.");

  String debugMessage = "Bell should ring after the timeout of "
    + String(bellTimeout / 1000.0)
    + "s";
  DEBUG_DEBUG(debugMessage.c_str());
  bell.ring();
}

WebhookClientConfig prepareWebhookConfig() {
  const char* environment = "testing";

  #ifdef PRODUCTION
    const char* environment = "production";
  #endif

  WebhookClientConfig config = {
    certificateAuthority,
    server,
    host,
    endpoint,
    batteryLevel.readLevel(),
    environment
  };

  return config;
}

Makefile

# Description: Makefile for the demo code
# Usage: make [PRODUCTION=1] [lint] [compile] [upload] [clean]
# With battery: make PRODUCTION=1
# With USB power: make

ifeq ($(PRODUCTION),1)
  BOARD := esp32:esp32:esp32c3
else
  BOARD := esp32:esp32:esp32c3:CDCOnBoot=cdc
endif
PORT ?= /dev/cu.usbmodem1410*
BUILD = build

EXTRA_FLAGS := $(if $(filter 1,$(PRODUCTION)),-DPRODUCTION=1,)

.PHONY: default lint display compile upload clean

default: lint compile upload clean

lint:
	cpplint --extensions=ino --filter=-legal/copyright,-runtime/int,-readability/todo *.ino

display:
	@echo "BOARD: $(BOARD)"
	@echo "PORT: $(PORT)"
	@echo "EXTRA_FLAGS: $(EXTRA_FLAGS)"

compile: clean lint display
	@if [ "$(PRODUCTION)" = "1" ]; then \
		echo "Compiling with extra flags..."; \
		arduino-cli compile --fqbn $(BOARD) --output-dir $(BUILD) --build-property build.extra_flags=$(EXTRA_FLAGS) ./; \
	else \
		echo "Compiling..."; \
		arduino-cli compile --fqbn $(BOARD) --output-dir $(BUILD) ./; \
	fi

upload:
	@if [ -n "$(PORT)" ]; then \
		arduino-cli upload --fqbn $(BOARD) --port $(PORT) --input-dir $(BUILD); \
	else \
		echo "Port $(PORT) is not available. Please check if another serial monitor is using it, or press BOOT-RESET button on board the PCB."; \
	fi

clean:
	rm -rf build

References