Друзья, привет! Прошу прощения за сложное название, попытаюсь объяснить мысль здесь. Итак, я новичок в разработке для ардуины (и, к сожалению, в электротехнике), но, скажем, не очень новичок в программировании. Решил навёрстывать путём сбора интересного для меня проекта, в качестве ориентира выбрал meteoClock от AlexGyver. На текущий момент, у меня даже нет в наличии ни одной ардуины, есть espruin-ка, по-сути "голая". И вот я начал вкуривать в тему, посмотрел исходники meteoClock. Сразу скажу, по опыту - работающая программа - хорошая программа. Но если для Alex это законченный проект, то для меня - будущий, а потому, сразу появились идеи как его переработать, улучшить и сделать по-своему. И вот, главное, что сейчас я хочу обсудить, это "управление состояниями" программы. Сейчас, meteoClock это т.н. "спагетти-код", его сложнее сопровождать и расширять, несмотря на многие очевидные плюсы. И вот у меня появился вопрос к знатокам, который лучше всего поясняется через черновую реализацию: возможно имеется смысл в таких встраиваемых системах реализовывать систему управления состояниями. Т.е. когда, каждый "экран", или "режим работы" представляет собой четкий кусок программы, который удобно поддерживать - потому что он самодостаточен. Конечно же вопрос только в эффективности - "для больших братьев" подобные решения практически обязательны, но в системах, где каждый байт - это серьёзно, наверняка не всё так очевидно. Но у меня нет опыта разработки для ответа на такой вопрос, а мысль есть, поэтому я сделал набросок (который, напомню, не могу протестировать, только скомпилировать ). Наверняка, всё что я сделал - это велосипед, но для изучения особенностей программирования для ардуины всё равно нужно что-то писать, а найденные в списке несколько fsm-библиотек мне, если честно, совсем не понравились.
Итак, главным будет класс "прошивка", в данном случае, это заготовка
Этот класс регистрирует два процесса MainProcess и SensorsProcess, каждый из которых реализует свою логику в методе update:
Благодаря этому, весь скетч выглядит очень просто и элегантно:
Под капотом: "параллельные" процессы, запуск, остановка, "пауза" и "продолжение", внутренний учет времени прошедшего с предыдущего запуска и даже возможность профилирования скорости выполнения процессов (т.е. система может выдавать в % сколько времени работал тот или иной процесс за последнюю секунду), а также возможность передачи "сообщений" (событий) какому-либо одному процессу или сразу всем.
При компиляции для Nano, получается
Sketch uses 8992 bytes (29%) of program storage space. Maximum is 30720 bytes.
Global variables use 458 bytes (22%) of dynamic memory, leaving 1590 bytes for local variables. Maximum is 2048 bytes.
Довольно много, наверное, жрёт LinkedList для динамических списков и наверняка можно многое оптимизировать, но главный вопрос - стоит ли оно того? Мне очень нравится систематизированность кода, каждое состояние (процесс) в отдельном классе, удобно расширять. Минус - память. Очень хочется услышать ваше мнение по вопросу!
Итак, главным будет класс "прошивка", в данном случае, это заготовка
MeteoClockFirmware:
class MeteoClockFirmware: public IFirmware {
public:
MeteoClockFirmware():IFirmware() {
this->registerFactory(PRC_MAIN, [](String id, IFirmware* host, IProcessMessage* msg) {
return (IFirmwareProcess*)(new MainProcess(id, host, msg));
}, true);
this->registerFactory(PRC_SENSORS, [](String id, IFirmware* host, IProcessMessage* msg) {
return (IFirmwareProcess*)(new SensorsProcess(id, host, msg));
});
#if (DEBUG_SERIAL == 1)
Serial.begin(9600);
#endif
}
void log(String msg) {
#if (DEBUG_SERIAL == 1)
Serial.println(msg);
#endif
}
};
MainProcess и SensorsProcess:
class MainProcess: public IMeteoClockProcess {
public:
MainProcess(String id, IFirmware* host, IProcessMessage* msg): IMeteoClockProcess(id, host, msg) {
this->isPaused = false;
this->log("MainProcess::start");
}
~MainProcess() {
// stop process
this->log("MainProcess::stop");
}
void update(unsigned long ms) {
if (!isPaused) {
this->log("MainProcess::run");
}
}
private:
bool isPaused;
};
class SensorsProcess: public IMeteoClockProcess {
public:
SensorsProcess(String id, IFirmware* host, IProcessMessage* msg): IMeteoClockProcess(id, host, msg) {
this->isPaused = false;
this->log("SensorsProcess::start");
}
~SensorsProcess() {
// stop process
this->log("SensorsProcess::stop");
}
void update(unsigned long ms) {
if (!isPaused) {
this->readSensors();
this->log("SensorsProcess::run");
}
}
void readSensors() {
// читаем показания датчиков
sensor1Value = random(10, 100);
sensor2Value = random(1000, 10000);
}
void pause() {
isPaused = true;
}
void unPause() {
isPaused = false;
}
private:
int sensor1Value;
int sensor2Value;
bool isPaused;
};
C++:
#include "meteo.h"
#include "meteo_main.h"
#include "meteo_sensors.h"
MeteoClockFirmware meteo;
void setup() {
}
void loop() {
meteo.run();
}
При компиляции для Nano, получается
Sketch uses 8992 bytes (29%) of program storage space. Maximum is 30720 bytes.
Global variables use 458 bytes (22%) of dynamic memory, leaving 1590 bytes for local variables. Maximum is 2048 bytes.
Довольно много, наверное, жрёт LinkedList для динамических списков и наверняка можно многое оптимизировать, но главный вопрос - стоит ли оно того? Мне очень нравится систематизированность кода, каждое состояние (процесс) в отдельном классе, удобно расширять. Минус - память. Очень хочется услышать ваше мнение по вопросу!