Автоматическая генерация программного кода микроконтроллера на основе событийно-ориентированной модели

Постановка задачи:
Создание сложной автоматизированной системы на основе контроллера для управления различной периферией (электронные замки, двигателя, светодиодные ленты и прочая электроника).

Создание данной системы потребовалась для квест комнаты, подобной этой, но в городе Хабаровск.
Наш квест в ином сеттинге, но в целом имеет примерно тот же набор исполнительных механизмов: реле, замки, ленты, герконы и т.д.

Основные требования к системе:

  • Надежность — при разработке сложных систем высока вероятность допустить трудно уловимые ошибки, чем больше код тем больше шанс пропустить ошибку и тем больше времени нужно на отладку, необходимо свести к минимуму вероятность некорректной работы.
  • Гибкость — возможность с минимальными временными затратами изменить логику работы
  • Функциональность — управление любым оборудованием и подключение любых сенсоров


image
В нашем квесте требовался достаточно сложный сценарий, в особенности:

  • Нелинейность сюжетной линии-игроки могут найти несколько различных путей решения головоломок,
  • Связанность — некоторые события должны происходить только после свершения ряда других игровых событий


image

Решение всех проблем было найдено во многом благодаря статье о «Парадигме ситуационно-ориентированного программирования», применив данный подход я получаю «из коробки»:

  • Гибкость — достаточно правильно построить архитектуру событий
  • Надежность — т.к. все события однотипны они могут быть реализованы схожим образом, используя простой код, надежность которого можно гарантировать
  • Функциональность — события вызывают действия, которые могут быть реализованы любым образом


Таким образом архитектуру системы можно построить на основе 3 видов базовых конструкций:

  • Событие,
  • Триггер,
  • Действие


При такой архитектуре можно заметить так же, что если задать в шаблоне кода триггера факт сохранения состояния (активирован\не активирован) то получаем в качестве бонуса возможность использовать состояние триггера в качестве события, таким образом можно построить сколь угодно сложную систему взаимосвязей событий и нелинейность алгоритма.

Благодаря выделения действий в отдельный функциональный блок получаем возможность еще на этапе разработки провести тестирование программного кода каждого действия по отдельности, отладку такого кода произвести намного легче, чем при отладке всего проекта. На отлаженный код уже будут ссылаться триггеры.

image

Триггеры в качестве условия срабатывания могут использовать любое сочетание событий или состояний триггеров (и\или\не).

События могут быть любыми (состояния портов, сообщения в serial и тд), в текущей реализации достаточно было состояния портов, поэтому пока оставлен только этот вариант, расширения функционала не займет много времени.
При создании системы было решено использовать доступное на местном рынке оборудование, таковыми оказались только платы Arduino, учитывая проблемы с доставкой почтой из других регионов факт наличия на рынке запасных частей имеет значение.

Приложение настроено на генерации кода скетча для данной платформы, но при желании адаптация под любой другой язык программирования займет лишь пару минут.
Таким образом шаблоны для скетча Arduino будут выглядеть в простейшем виде например так:

Основной код программы
@init
void setup() {
@runonce
}

void loop() { 
@loopcode
}

@triggers
@sensors
@actions



Шаблон кода действия
//@description
void @name()
{       
bool debug=true;
//@id
@code
//@id
  if (debug) {
    Serial.println("DoAction @name");
  }
}



Шаблон кода триггера
//@description
bool @nameActivated=false;
bool @name(){
        if (@nameActivated){
                return true;
        }else
        {       
                if (@event){
                        @nameActivated=true;
                        Serial.println("@name Activated");
                        @nameDoAction();
                        return true;
                }
                return false;
        }       
        return false;
}

void @nameDoAction(){
@nameActivated=true;
//******************************************
@actions
//******************************************
}



Шаблон кода сенсора
//@description
bool @name()
{
        int @namePin=@pinNumber;
        pinMode(@namePin, INPUT_PULLUP);
        int sensorVal = digitalRead(@namePin);
        if (sensorVal == @trueval) {return true;}else{return false;}
}



Пример шаблона инициализации
В данном шаблоне можно подключить библиотеки и объявить глобальные для всего проекта переменные и функции.
#include <etherShield.h>
#include <ETHER_28J60.h>
#include <EEPROM.h>
static uint8_t mac[6] = {0x54, 0x55, 0x58, 0x10, 0x10, 0x24};  
static uint8_t ip[4] = {192, 168, 137, 15};    
static uint16_t port = 80;  
ETHER_28J60 ethernet;
bool started=false;



На основе данных шаблонов в окне приложения можно задать все параметры сценария.

image

После экспорта прошивки мы можем получить подобный код:

Плюс такого подхода в том, что все блоки действий на момент экспорта прошивки уже отлажены еще на этапе их разработки, все блоки триггеров, сенсоров и обертки кода действий отлажены и код компилируется без ошибок и будет работать корректно сразу, без этапа отладки.

На практике при разработке квест комнаты много раз приходилось изменять сценарий, благодаря приложению изменять код прошивки не требовалось, достаточно изменить параметры триггеров в интерфейсе программы.

Фотографий с самого квеста пока не прикладываю, если будет желание могу приложить. На данный момент оборудование с прошивкой созданной по данной схеме отработало уже более 50 игровых циклов без единого сбоя.
Исходные коды приложения открыты и доступны всем желающим.

Тестовое приложение с типовыми настройками шаблонов можно найти на github.

Надеюсь, данная статья поможет тем, кто решит создать систему для автоматизации различных процессов, будь то умный дом, охранная система или что либо другое.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

© Habrahabr.ru