Implementing the Dezyne model on the ESP32 chip

Building a Smart Alarm System with Dezyne and ESP32

Learn how to use the Verum Dezyne model to create a simple yet fully functional alarm system on the ESP32 board using the Arduino Framework. This tutorial covers entire process of integrating Dezyne model within target platform. We’ll build an alarm with features like:

  • PIN-based Security: Enter a valid PIN to activate or deactivate the alarm.
  • Motion Detection: The alarm triggers when a PIR sensor detects movement while armed.
  • Grace Period: A timer provides a delay after arming before the alarm activates, giving you time to settle in.
  • Audible and Visual Alerts: The system utilizes a buzzer as a siren and an LED to indicate different states.

System Overview:

Alarm System Requirements

  1. System starts disarmed.
  2. Entering a valid PIN triggers validation. If correct, the system arms and starts a 5-second timer.
  3. Once armed, the system monitors for movement.
  4. Movement detection initiates a 5-second grace period before activating the siren.
  5. The system can be disarmed at any stage using the correct PIN.

Setup Requirements

Hardware Setup

Running the project

1. Opening DezyneAlarm project using PlatformIO

  1. Open Visual Studio Code with PlatformIO installed
  2. Open cloned/downloaded DezyneAlarm root directory
  3. Wait few seconds for PlatformIO to initialize the project

2. Dezyne model:

For the demostration purposes I modified the Alarm example from the Verum Dezyne examples folder.
This example comes with alarm-interfaces.dzn which defines interfaces for console, pin, sensor, siren and timer. Then alarm.dzn defines main alarm component with all the ports and behavior:

3. Generating C++ Code:

  1. If we made some modifications to Dezyne model it is necesserary to regenerate the C++ code. Before that we need to configure output of the generated code in the Dezyne extension settings.
    In this case target directory is the src/
  2. After that we can freely generate code from Dezyne model and new change will be applied:

4. Copying C++ runtime files:

  1. IMPORTANT: This project already comes with all runtime files included and you can safely ignore this step if you are not starting new project from scratch.
  2. To run Dezyne-generated code we need to use dedicated runtime/locator files. Those are available in the Verum Dezyne installation root directory in /runtime/c++. In order to use it we need to copy all the files and dirs to our project src/ directory:

5. Integrating generated C++ code

DezyneAlarm project arrives fully implemented in /src/main.cpp file. To explain how our generated Dezyne code integrates within /src/main.cpp I will explain few steps that have to be taken only once:

Include Dezyne locator, runtime and generated code file with main alarm component:

#include <dzn/locator.hh>
#include <dzn/runtime.hh>
#include "alarm.hh"

Declare Dezyne runtime and locator, create empty pointer for AlarmController Dezyne component:

dzn::runtime runtime;
dzn::locator loc;
std::unique_ptr<AlarmController> alarm_comp;

Initialize Dezyne locator, runtime and alarm component.

    loc.set(runtime);
    alarm_comp = std::unique_ptr<AlarmController>(new AlarmController(loc));

Setup callbacks for the Dezyne alarm component. In other words - provide handlers for the events/triggers we have specified in the interfaces. This might be either simple startBuzzer() or a more complex timer implementation which capture defined arming/grace delay and starts the background process that will trigger the alarm_comp->timer.out.timeout() event once delay is reached:

void setupDezyneComponent(){
    // Setup callbacks for the Dezyne alarm component
    // Dezyne generated code comes with empty handlers for the system ports
    alarm_comp->auth.in.valid = [](int pin) {
        // Terrible way of validating PIN
        Serial.println("Checking PIN...");
        bool valid = pin == VALID_PIN;
        Serial.println(valid ? "PIN valid." : "PIN invalid.");
        return valid;
    };

    alarm_comp->timer.in.set = [](int ms_delay){
        // Set a delay for actions like arming or triggering the siren
        // Dezyne allows direct access to internal state variables
        if (alarm_comp->console.state == IConsole::State::Disarmed)
          Serial.print("Arming in ");
        else
          Serial.print("Starting siren in ");
        Serial.print(ms_delay / 1000);
        Serial.println(" seconds...");
        alarmTimer.set(ms_delay, [](){ 
          // Use Dezyne timeout event as a callback
          alarm_comp->timer.out.timeout(); 
        });
    };

    alarm_comp->timer.in.cancel = [](){
        alarmTimer.cancel();
    };

    alarm_comp->siren.in.enable = [](){
        Serial.println("Siren Enabled!");
        startBuzzer();
        controlStatusLED(PWM_FREQUENCY_FAST, DUTY_CYCLE_HALF);
    };

    alarm_comp->siren.in.disable = [](){
        Serial.println("Siren Disabled!");
        stopBuzzer();
    };

    alarm_comp->console.out.detected = [](){
        Serial.println("Burglar Detected!");
        controlStatusLED(PWM_FREQUENCY_SLOW, DUTY_CYCLE_HALF);
    };

    alarm_comp->console.out.armed = [](){
        Serial.println("Alarm Armed!");
        controlStatusLED(PWM_FREQUENCY_SLOW, DUTY_CYCLE_FULL);
    };

    alarm_comp->console.out.disarmed = [](){
        Serial.println("Alarm Disarmed!");
        controlStatusLED(PWM_FREQUENCY_SLOW, DUTY_CYCLE_OFF);
    };

    alarm_comp->console.out.arming = [](){
        controlStatusLED(PWM_FREQUENCY_SLOW, DUTY_CYCLE_HALF);
    };
}

If user arms/disarm alarm using PIN code, call the console.in.arm with provided PIN:

Serial.println(inputString); // Debug print
int receivedPin = inputString.toInt();
// Trigger Dezyne alarm component with entered PIN
alarm_comp->console.in.arm(receivedPin);

All the remaining code is “standard” and does not have to be modified. Idea is to use Dezyne generated code as plug-and-play component which integrates with user interface and all the requried sensors/actuators.

6. Building, uploading and testing

Once we are done with coding we can compile and upload our project using the buttons from the bottom ribbon:

Succesfully compiled and uploaded code:

Once connected ESP32 is flashed we can start serial monitor and type PIN “1234” to arm our system:

7. Detailed behavior description

1. Disarmed State:

  • The system starts in the disarmed state.
  • The serial monitor might display an initial message indicating the system is ready.
  • The LED (if connected) is typically off or shows a specific color/pattern to represent the disarmed state (refer to your code’s LED control logic).

2. Arming the System:

  • Enter your PIN through the serial monitor (type the numbers and press Enter).
  • The system checks the PIN validity using the defined callback function.
  • If the PIN is correct, the system enters the “arming” state:
    • A message is displayed on the serial monitor indicating the system is arming.
    • The LED might change color/pattern to visually represent the arming process (refer to code).
    • A 5-second timer starts, and the countdown is displayed on the serial monitor (optional).

3. Armed State:

  • After the 5-second timer elapses, the system transitions to the “armed” state:
    • A message is displayed on the serial monitor indicating the system is armed.
    • The LED might change color/pattern again to represent the armed state (refer to code).
    • The system starts monitoring for movement using the PIR sensor.

4. Movement Detection:

  • If the PIR sensor detects movement while the system is armed:
    • A message is displayed on the serial monitor indicating movement is detected.
    • The LED might change color/pattern again to represent movement detection (refer to code).
    • A 5-second grace period timer starts, and the countdown is displayed on the serial monitor (optional).

5. Siren Activation:

  • After the grace period elapses without disarming the system:
    • The siren (buzzer) activates, producing an audible alert.
    • A message is displayed on the serial monitor indicating the siren is enabled.
    • The LED might change color/pattern again to represent the active siren state (refer to code).

6. Disarming the System:

  • You can disarm the system at any point, even during the arming or grace period:
    • Enter the correct PIN through the serial monitor.
    • If the PIN is valid, the system transitions to the disarmed state:
      • The siren (if active) deactivates.
      • Messages are displayed on the serial monitor indicating disarming and the current state.
      • The LED might change color/pattern again to represent the disarmed state (refer to code).

Additional Notes:

  • The specific behavior and visual/audio cues (LED colors, messages, etc.) might vary depending on your code’s implementation. Refer to the comments and logic within your code for details.
  • You can experiment with different functionalities and customize the system’s behavior based on your preferences.
  • Remember to follow safe practices when working with electronics and ensure proper component connections and power supply.

By following these steps and understanding the interactions, you can effectively run the Dezyne-based alarm system and make adjustments to personalize and enhance its functionality.