ARDUINO Простой, лёгкий ООП-фреймворк для прошивок Arduino

Jumangee

✩✩✩✩✩✩✩
14 Дек 2020
11
3
Друзья, привет! Прошу прощения за сложное название, попытаюсь объяснить мысль здесь. Итак, я новичок в разработке для ардуины (и, к сожалению, в электротехнике), но, скажем, не очень новичок в программировании. Решил навёрстывать путём сбора интересного для меня проекта, в качестве ориентира выбрал meteoClock от AlexGyver. На текущий момент, у меня даже нет в наличии ни одной ардуины, есть espruin-ка, по-сути "голая". И вот я начал вкуривать в тему, посмотрел исходники meteoClock. Сразу скажу, по опыту - работающая программа - хорошая программа. Но если для Alex это законченный проект, то для меня - будущий, а потому, сразу появились идеи как его переработать, улучшить и сделать по-своему. И вот, главное, что сейчас я хочу обсудить, это "управление состояниями" программы. Сейчас, meteoClock это т.н. "спагетти-код", его сложнее сопровождать и расширять, несмотря на многие очевидные плюсы. И вот у меня появился вопрос к знатокам, который лучше всего поясняется через черновую реализацию: возможно имеется смысл в таких встраиваемых системах реализовывать систему управления состояниями. Т.е. когда, каждый "экран", или "режим работы" представляет собой четкий кусок программы, который удобно поддерживать - потому что он самодостаточен. Конечно же вопрос только в эффективности - "для больших братьев" подобные решения практически обязательны, но в системах, где каждый байт - это серьёзно, наверняка не всё так очевидно. Но у меня нет опыта разработки для ответа на такой вопрос, а мысль есть, поэтому я сделал набросок (который, напомню, не могу протестировать, только скомпилировать :rolleyes: ). Наверняка, всё что я сделал - это велосипед, но для изучения особенностей программирования для ардуины всё равно нужно что-то писать, а найденные в списке несколько fsm-библиотек мне, если честно, совсем не понравились.

Итак, главным будет класс "прошивка", в данном случае, это заготовка

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, каждый из которых реализует свою логику в методе update:
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 для динамических списков и наверняка можно многое оптимизировать, но главный вопрос - стоит ли оно того? Мне очень нравится систематизированность кода, каждое состояние (процесс) в отдельном классе, удобно расширять. Минус - память. Очень хочется услышать ваше мнение по вопросу! :)
 

poty

★★★★★★✩
19 Фев 2020
2,994
895
Ух ты! Отличное начало! Мне нравится, хоть кто-то занялся серьезным программированием!
 

Александр Симонов

★★★★✩✩✩
2 Авг 2018
727
207
С первого взгляда похоже на ООП-обертку для FreeRTOS. А есть метод сделать delay неблокирующий внутри таска (процесса)?
 

Jumangee

✩✩✩✩✩✩✩
14 Дек 2020
11
3
Сделал некоторую оптимизацию уменьшив футпринт и добавил немного примеров для ответа на вопрос.

Итак, теперь скетч состоит из одной строки:
C++:
#include "meteo.h"
#include "meteo_main.h"
#include "meteo_sensors.h"

void setup() {
}

void loop() {
    MeteoClockFirmware::get()->run();
}
Это сделано для того, чтобы нельзя было заплодить несколько объектов прошивки, а это ограничение позволяет немного съэкономить память (вроде бы)

Теперь, по поводу того, как реализован процесс обработки процессов: в прошивке из run вызывается метод run каждого процесса, в котором по каждому процессу проверяется - не на паузе ли он, и если не на паузе - выполняется обработчик update:
IFirmwareProcess::run:
unsigned long run(unsigned long start) {
    unsigned long ms = start - this->lastUpdate;
    if (this->pausedUpTo != NULL && start < this->pausedUpTo) {
        return start;    // no time wasting
    } else if (this->pausedUpTo != NULL && this->pausedUpTo > 0) {
        this->unPause();
    }
    this->update(ms);
    unsigned long endTime = millis();
    #if defined(DEBUG_PRO_MS)
    this->usedMs += endTime - start;
    #endif
    this->lastUpdate = endTime;
    return endTime;
}
Теперь, обновленные классы с примерами прямого менеджмента и через сообщение:

MeteoClockFirmware (прошивка):
#define PRC_MAIN "PRC_MAIN"
#define PRC_SENSORS "PRC_SENSORS"

class MeteoClockFirmware: public IFirmware {
    MeteoClockFirmware(): IFirmware() {

        this->registerFactory(PRC_MAIN, [](String id, IProcessMessage* msg) {
            return new MainProcess(id, msg);
        }, true);
       
        this->registerFactory(PRC_SENSORS, [](String id, IProcessMessage* msg) {
            return new SensorsProcess(id, msg);
        });
       
        this->addProcess(PRC_MAIN);
        this->addProcess(PRC_SENSORS);
       
        this->pauseProcess(PRC_SENSORS, 1000);
           
        #if (DEBUG_SERIAL == 1)
        Serial.begin(9600);
        #endif
    }
   
    public:
   
        void log(String msg) {
            #if (DEBUG_SERIAL == 1)
            Serial.println(msg);
            #endif
        }
       
        static IFirmware* get() {
            IFirmware* f = IFirmware::instance;
            if (f == NULL) {
                IFirmware::instance = new MeteoClockFirmware();
            }
            return f;
        }
};
MainProcess:
#include "meteo_messages.h"

class MainProcess: public IMeteoClockProcess {
  public:
      MainProcess(String id, IProcessMessage* msg): IMeteoClockProcess(id, msg) {
          this->log("MainProcess::start");
      }
     
      ~MainProcess() {
          // stop process
          this->log("MainProcess::stop");
      }
     
      void update(unsigned long ms) {
        this->log("MainProcess::run ");
        if (random(1, 10) == 5) {
            this->getHost()->pauseProcess(PRC_MAIN, 2000);
        }
      }
   
    bool handleMessage(IProcessMessage* msg) {
        if (msg->getType() == "TEXT_MESSAGE") {
            TextMessage* cmd = (TextMessage*)msg;
            if (cmd->getText() == "UNPAUSE_ALL") {
                this->unPause();
                return true; // message handling done
            }
            if (cmd->getText() == "STOP_ALL") {
                this->getHost()->stopAll();
                return true; // message handling done
            }
        }
        return false;
    }
};
SensorsProcess:
class SensorsProcess: public IMeteoClockProcess {
  public:
      SensorsProcess(String id, IProcessMessage* msg): IMeteoClockProcess(id, msg) {
          this->log("SensorsProcess::start");
      }
     
      ~SensorsProcess() {
          // stop process
          this->log("SensorsProcess::stop");
      }
     
      void update(unsigned long ms) {
        this->log("SensorsProcess::run");
        this->readSensors();
        if (this->sensor1Value == 50) {
            this->getHost()->sendMessage(new TextMessage("UNPAUSE ALL"));
        }
      }
     
      void readSensors() {
          // читаем показания датчиков
          sensor1Value = random(10, 100);
          sensor2Value = random(1000, 10000);
      }
   
    private:
        int sensor1Value;
        int sensor2Value;
};
messages:
class IProcessMessage {
    public:
        IProcessMessage(IFirmwareProcess* from, String type) {
            this->sender = from;
            this->type = type;
        }
       
        String getSenderId();
       
        bool isSenderId(String compareTo);
       
        IFirmwareProcess* getSender() {
            return sender;
        }
       
        bool isAnonymous() {
            return getSender() == NULL;
        }
       
        String getType() {
            return this->type;
        }

    private:
        IFirmwareProcess* sender;
        String type;
};

////

class TextMessage: public IProcessMessage {
    public:
        TextMessage(IFirmwareProcess* from, String text): IProcessMessage(from, "TEXT_MESSAGE") {
            this->text = text;
        }
       
        TextMessage(String text): IProcessMessage(NULL, "TEXT_MESSAGE") {
            this->text = text;
        }
       
        String getText() {
            return this->text;
        }

    private:
        String text;
};
 
  • Лойс +1
Реакции: kostyamat и sshkalikov

sshkalikov

✩✩✩✩✩✩✩
21 Дек 2020
5
4
Выглядит солидно на мой вкус. Только из-за того, что TextMessage получился 'stringly typed', в код уже закралась опечатка, которую не отловил компилятор ("UNPAUSE ALL" вместо "UNPAUSE_ALL").

Аналогично, имея, скажем так, некоторый опыт промышленной разработки (не под МК) и начав знакомиться с ардуино, первым делом испытал лютую резь в глазах, глядя на разные публикуемые здесь и не здесь примеры кода. И тоже собирался было городить какой-нибудь свой велосипед, но вообще-то велосипедов вроде как уже хватает. Сейчас вот читаю доку к https://github.com/arkhipenko/TaskScheduler - выглядит неплохо, хочу опробовать. Есть вообще серьезный вариант https://github.com/feilipu/Arduino_FreeRTOS_Library, но он сам по себе, как я понял, выжрет большую часть ресурсов ардуины.
Интересно было бы послушать опытных ардуинокодеров насчет общепринятых архитектурных подходов и библиотек/фреймворков для их реализации.
 

Jumangee

✩✩✩✩✩✩✩
14 Дек 2020
11
3
TextMessage получился 'stringly typed', в код уже закралась опечатка
Спасибо, действительно плохое решение, легко заменяется, например на:
CmdMessage:
#define MSG_COMMAND_ALL "COMMAND_MESSAGE"
#define MSG_TEXT_ALL "TEXT_MESSAGE"

class CmdMessage: public IProcessMessage {
    public:
        enum Cmd {
            UNPAUSE_ALL,
            PAUSE_ALL
        };
        
        CmdMessage(IFirmwareProcess* from, CmdMessage::Cmd cmd): IProcessMessage(from, MSG_COMMAND_ALL) {
            this->cmd = cmd;
        }
        
        CmdMessage(CmdMessage::Cmd cmd): IProcessMessage(NULL, MSG_COMMAND_ALL) {
            this->cmd = cmd;
        }
        
        Cmd getCmd() {
            return this->cmd;
        }

    private:
        Cmd cmd;
};
К сожалению, пока не вижу хорошего и расширяемого способа проверки типа сообщения, возможно сочетание текстового значения и define-констант будет достаточно.

Что касается приведенных примеров - да, несомненно эти движки посерьёзнее, но и размер тоже и сложность в использовании. Быть может, стоит искать нишу где-то между "нет движка" и "монстр под капотом" с поблажкой на относительную простоту использования? :)
 

kDn

★★★★★✩✩
18 Ноя 2019
1,103
437
Интересно было бы послушать опытных ардуинокодеров насчет общепринятых архитектурных подходов и библиотек/фреймворков для их реализации.
Будет желание можно попробовать фреймворк для ESP8266/ESP32 WebUI, названный EmbUI, на нем у нас лампа сделана :)
https://github.com/DmytroKorniienko/EmbUI .
Ну и сама лампа периодически перерабатывается и переписывается. Под ESP32 и FreeRTOS точно планируем делать.

* Только меня особо опытным в разработки под контроллеры назвать сложно, поскольку чуть иная специализация... Все чем занимаюсь - хобби и развлечение. Но вроде выходит более-менее нормально.
 
  • Лойс +1
Реакции: Jumangee

Jumangee

✩✩✩✩✩✩✩
14 Дек 2020
11
3
Камрады, нужна ваша помощь! SOS! )))

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

В общем, репозиторий всего проекта лежит на https://github.com/jumangee/MeteoClock
Но там велосипед на велосипеде: исходники из src собираются и пре-парсятся скриптами grunt, после чего формируется каталог MeteoClock готовый к сборке (в grunt есть команды arduino-cli для сборки и заливки на девайс)

Программа нормально собирается и заливается на устройство, запускается, но... я ловлю какие-то странные глюки в буквальном смысле на ровном месте (((

C++:
void loop() {
    MeteoClockFirmware::get()->run();
}
MeteoClockFirmware::get() создаёт экземпляр класса, где добавляются две анонимные ф-ии для создании экземпляров процессов

meteo.cpp:
void MeteoClockFirmware::init() {
    FACTORY_DEFAULT(PRC_MAIN, MainProcess)
    FACTORY(PRC_SENSORS, EnvironmentSensorsProcess)
    
    this->addProcess(PRC_MAIN);
    this->addProcess(PRC_SENSORS);
}
Так вот эти процессы никак не хотят работать, они как будто "убивают" систему и она как будто бы обнуляется, запуская всё по-новой:

C++:
new MeteoClockFirmware
START
IFirmware::addProcess//1
IFirmware::addProcess//2
IFirmware::createProcess::findFactoryRegistration: PRC_MAIN
IFirmware::createProcess//factory/!
IFirmwanew MeteoClockFirmware
START
IFirmware::addProcess//1
IFirmware::addProcess//2
IFirmware::createProcess::findFactoryRegistration: PRC_MAIN
IFirmware::createProcess//factory/!
IFirmwanew MeteoClockFirmware
START
IFirmware::addProcess//1
IFirmware::addProcess//2
IFirmware::createProcess::findFactoryRegistration: PRC_MAIN
IFirmware::createProcess//factory/!
IFirmware::createProcess//factory
IFirmware::addProcess//newProcess
IFirmware::addProcess//1
(вот здесь new MeteoClockFirmware быть не должно!!!)

Я не понимаю ЧЯДНТ, памагити!
 

Jumangee

✩✩✩✩✩✩✩
14 Дек 2020
11
3
После долгих ковыряний кажется я понял... что имел в виду Билл Гейтс в 87ом))
Похоже, мне мало памяти и я не понимаю чем я её так засираю
:cry:

Sketch uses 12956 bytes (42%) of program storage space. Maximum is 30720 bytes.
Global variables use 877 bytes (42%) of dynamic memory, leaving 1171 bytes for local variables. Maximum is 2048 bytes.
 

kDn

★★★★★✩✩
18 Ноя 2019
1,103
437
Похоже, мне мало памяти и я не понимаю чем я её так засираю
Проверяйте хранение и передачу строк в первую очередь. Константы должны жить во флеше, а через стек обмен - указатели и ссылки, а не через объекты.
 
  • Лойс +1
Реакции: Jumangee

Jumangee

✩✩✩✩✩✩✩
14 Дек 2020
11
3
Всем большое спасибо! Проект заработал, вроде бы стабильно. Здраво оценив излишность пользования строк для хранения ид процесса, а также для ид типов сообщений (привычка к удобство от большого брата) отказался от их использовании совсем, ну и конечно же константы строк для дебага перенес в прогмем. Теперь перед загрузкой в мозги используется ~500байт памяти, а "под нагрузкой", по замерам memoryfree свободно 65% памяти (~1330б). Это при двух потоках, один - держит SSD1306Ascii, второй работает c Adafruit_BME280 (раз в 5 сек поток просыпается, меряет показания и отправляет сообщения в основной поток, которых отображает их на экране).
Особенно (мне) нравится выделение работы сущностей в отдельные классы, уже вырисовывается что-то вроде набора (библиотеки?) процессов-шаблонов, на основе которых удобно собирать прошивки для разных целей. Вот вчера пытался паять bme280 к проводам, убил его (с паяльник я только начинаю теплые отношения :cry:) теперь переношу его "в загашник", а вмето него сделаю класс для работы с ccs811, при этом в основной части программы изменится только название второго создаваемого потока.
Фреймворк следит за загрузкой потоков и рассылает сообщение с информацией о кол-ве свободной памяти, так главный поток может отображать эти данные на экране вместе с остальной информацией.
Пока не могу решить изменить ли логику работы обработки сообщений. Дело в том, что сейчас, когда поток отправляет сообщение, то, по-сути, этот поток передаёт управление тем, кто будет это сообщение обрабатывать, т.е. "за чужой счет" другие потоки могут выполнять обработку. Это и плюс (потоки могут просыпаться, или общаться друг с другом сразу, требуется меньше памяти) и минус - породивший сообщение поток останавливается до завершения обработки сообщения. Вот думаю - оставить это как есть? Ведь в обработчике сообщений можно только "сохранить переданные данные", без обработки. Или же сделать некий кэш сообщений сгенерированных потоками на каждом такте, и после завершения такта обработки - выполнить рассылку сообщений? Или вообще, передавать этот кэш сообщений в ф-ию такта при каждом вызове, а они уже сами пройдутся по входящим сообщениям?
 

Jumangee

✩✩✩✩✩✩✩
14 Дек 2020
11
3
Трудно поверить, что уже прошло больше года, а ведь уже тогда "на руках чтото было"))

Проект сильно изменился, но более-менее оформился и разделился на непосредственно "фреймворк" и сделанный на нём проект EcoSense@clock - метеостанция с кучей датчиков качества воздуха.

Теперь же передо мной стал вопрос - нужно ли развивать проект фреймворка в сторону общего проекта? Нужен ли такой, легковесный ООП-фреймворк народу?

Если да - с чего начинать? Исходники уже есть на гитхабе, есть страницы на hackaday, но это чтото не даёт понимания - а нуна ли оно народу? ))
И не понятно, как это оценить)

Приведу несколько примеров, как это всё "выглядит" на примере прошивки для метеостанции (а это 9 датчиков, дисплей, работа с wifi и управление питанием - и каждый в своем, отдельном потоке)

ino-скрипт:
#include "ecosenseatclock.h"

void loop() {
    EcosenseAtClockFirmware::get()->run();
}
Класс прошивка - без лишнего:
class EcosenseAtClockFirmware: public IFirmware {
    const static byte PwrMngmtPins[];
    const static byte AdcMuxMngmtPins[];

    //@cpp
const static byte EcosenseAtClockFirmware::PwrMngmtPins[] = PWRMNGMTPINS;
    //@cpp
const static byte EcosenseAtClockFirmware::AdcMuxMngmtPins[] = ADCMUXPINS;

    //@implement
    //@include <Arduino.h>
    EcosenseAtClockFirmware(): IFirmware() {
        #if PROCESSY_DEBUG_SERIAL == 1
        Serial.begin(9600);
        delay(1000);       
        TRACELNF("START");
        #endif

        analogReference(EXTERNAL);    // important!

        // ---[ TASK REGISTRATION ]---

        PROCESS_REG(DisplayProcess);
        PROCESS_REG(RTClockProcess);
        #if NOWIFI_BUILD != 1
            PROCESS_REG(WifiProcess);
        #endif
        PROCESS_REG(BME280SensorProcess);
        PROCESS_REG(EcoSenseAtClockBtnProcess);
        PROCESS_REG(MHZ19SensorProcess);

        #if SLIM_BUILD != 1
            PROCESS_REG(PwrConsumer1Process);
            PROCESS_REG(PwrConsumer2Process);
            PROCESS_REG(PwrConsumer3Process);

            PROCESS_REG(MQ136SensorProcess);
            PROCESS_REG(MQ7SensorProcess);
            PROCESS_REG(MQ135SensorProcess);
            PROCESS_REG(MQ4SensorProcess);
            PROCESS_REG(PPD42SensorProcess);
            PROCESS_REG(CJMCU1100SensorProcess);
        #endif

        // ---[ STARTUP ]---

        addProcess(PRC_DISPLAY);
        addProcess(PRC_RTC);
        #if NOWIFI_BUILD != 1
            addProcess(PRC_WIFI);
        #endif
        addProcess(PRC_BME280);
        addProcess(PRC_BTN);
        addProcess(PRC_MHZ19);

        #if SLIM_BUILD != 1
            PowerloadManagement::init(ARR2PTR(EcosenseAtClockFirmware::PwrMngmtPins));
            ADCMuxManagement::init(EcosenseAtClockFirmware::AdcMuxMngmtPins);
        
            addProcess(PRC_CONSUMER1);
        #endif
    };
    
    public:
        //@implement
        static IFirmware* get() {
            if (IFirmware::instance == NULL) {
                IFirmware::instance = new EcosenseAtClockFirmware();
            }
            return IFirmware::instance;
        }

    protected:
        //@implement
        //@include "stuff.h"
        //@include "MemoryFree.h"
        virtual void handlerProcessDebugTimer(unsigned long dT) {
            byte processCount = 0;
            for (uint16_t i = 0; i < this->processListSize; i++) {
                IFirmwareProcessRegistration* reg = this->processList[i];
                if (reg->isActive()) {
                    processCount++;
                }
            }
            this->sendMessage(new SelfReportMessage(processCount, freeMemory()));
        }

};
Всё MQ-сенсоры работают унифицированно:
class MQ135SensorProcess: public MQSensorProcess {
    public:
        PROCESSID(PRC_MQ135);
        
        //@implement
        //@include "ecosense_cfg.h"
        MQ135SensorProcess(IProcessMessage* msg): MQSensorProcess(MUXCHANNEL_MQ135, msg) {
            this->pause(MQ135_PREBURN_TIMEOUT);    // pre-burn timeout
            TRACELNF("MQ135: pre-burn timeout")
        }

        //@implement
        static IFirmwareProcess* factory(IProcessMessage* msg) {
            return new MQ135SensorProcess(msg);
        }

        //@implement
        //@include "ecosense_messages.h"
        IProcessMessage* getResultMsg() {
            return getSimpleResultMsg(AirQualityMsg::GasType::COMMON, this->getVoltage());
        }
};
Обработка touch-button sensor:
class EcoSenseAtClockBtnProcess: public ADCMuxButtonProcess {
    public:
        PROCESSID(PRC_BTN);

        //@implement
        EcoSenseAtClockBtnProcess(IProcessMessage* msg): ADCMuxButtonProcess(MUXCHANNEL_BTN, ADCMUX_SIGNAL_PIN, msg) {
        }

        //@implement
        static IFirmwareProcess* factory(IProcessMessage* msg) {
            return new EcoSenseAtClockBtnProcess(msg);
        }
};
Работа с BME280 (без лишнего):
class BME280SensorProcess: public IFirmwareProcess {
    private:
        ForcedClimate climateSensor;

    public:
        PROCESSID(PRC_BME280);

        //@implement
        BME280SensorProcess(IProcessMessage* msg): IFirmwareProcess(msg) {
            climateSensor.begin();
        }

        //@implement
        static IFirmwareProcess* factory(IProcessMessage* msg) {
            return new BME280SensorProcess(msg);
        }

        //@implement
        //@include "ecosense_messages.h"
        //@include "forcedClimate/forcedClimate.cpp"
        void update(unsigned long ms) {
            climateSensor.takeForcedMeasurement();
            this->sendMessage(new EnvDataMessage(climateSensor.getTemperatureCelcius(), climateSensor.getRelativeHumidity(), climateSensor.getPressure() / 1.33));

            this->pause(8000);
        }
};
Здесь в коде можно увидеть специальные мета-команды //@implement и //@include - это для написанного парсера, который позволяет писать весь код в одном .h-файле, а перед компиляцией собираются .h и .cpp, так уменьшается количество файлов и кодить проще (имхо)

Пожалуйста, откликнитесь - интересно ли такое кому? Хочется "дать добро людям", но опыта с open-source мало, не знаю как это правильно делать))
 

kDn

★★★★★✩✩
18 Ноя 2019
1,103
437
@Jumangee, шансы что вашими наработками будут пользоваться другие - околонулевые))). Проверено.
Дело в том, что если готовая прошивка выполняющая какую-то задачу может быть потенциально интересна многим, то инструмент облегчающий работу - интересен считанным единицам. Как следствие - вне зависимости от того сколько вы времени потратите, насколько подробные примеры и описание сделаете - просто не будут использовать и все тут))). Одни по незнанию, другие по непониманию, третьи обязательно будут уверены, что смогут изобрести велосипед гораздо лучше чем уже есть.

В общем максимум на что можете рассчитывать - это пару человек которые возможно присоединяться к совместной работе, в том случае если будут видеть реальные плюсы для СВОИХ задач, и то вряд ли. Я не просто так это говорю - а проверено на этом форуме. То ли уровень знаний здесь недостаточный, то ли народ ленивый - но факт фактический - за год лишь пару человек заинтересовались нашим фреймворком, а уж разработчиков самого фреймворка можно посчитать на пальцах одной руки, да и то запас останется.

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

Ну и отдельно хотелось бы отметить, что вы выбрали не сильно удачный контроллер для реализации... У него мало ресурсов и это сильно ограничивает сферу применения, я боюсь что достоинства вашей работы могут не перевесить тот оверхед по ресурсам памяти и процессорного времени которые неизбежно возникнут при использовании подхода менеджера задач. Как-то так.
 
  • Лойс +1
Реакции: romrs, Sergo_ST и bort707

Jumangee

✩✩✩✩✩✩✩
14 Дек 2020
11
3
Спасибо! Я в целом всё это прекрасно понимаю и делаю в общем-то для себя. Мне просто надо понять как делать (дальше) для себя так, чтобы это было интересно бОльшему числу людей? и не cтолько на этом сайте, больше рассчитываю на hackaday.

Дальше, есть мысли перейти на ESP32 и адаптировать фреймворк и под него тоже, просто пока контроллера нет в наличии.
 

bort707

★★★★★★✩
21 Сен 2020
2,902
863
@Jumangee, цель то у вас какая? Можете в трех словах описать, какие преимущества дает ваш фреймворк по сравнению с обычным IDE ардуино?

Я немного добавлю к тому, что написал выше @kDn, только чуть с другой точки зрения. Я полностью согласен с его мыслью, что разработка подобных систем - в 99% случаев работа исключительно на себя, другим в большинстве случаев это не нужно. Видите ли, тем, кому ваша система скорее всего пригодилась - это начинающим - она скорее всего покажется слишком сложной. Большинство и те жалкие 20 конструкций, что есть в стандартном ардуино - не успевают изучить до того, как теряют интерес к ардуино вообще. А продвинутые пользователи - вот взять например меня (нескромно?) - испытывают недоверие к сложным системам, написанным начинающими, да и средними по уровню программистами. Вместо того чтоб изучать подобные "фреймворки" и постоянно натыкаться на разные косяки - я уж лучше буду писать в чистом неулучшенном ардуино - славо богу а автоматном стиле я могу писать и без фреймворков.
На мой взгляд, большинство подобных начинаний, во-первых, редко дорастают до чето-то серьезного, и во-вторых, всегда отдают некой кустарщиной.
 
  • Лойс +1
Реакции: Sergo_ST

Jumangee

✩✩✩✩✩✩✩
14 Дек 2020
11
3
Цель? Нести людям добро)))
Фреймворк создан с целью избавления от спагетти-кода, добавления возможности модульности в духе do not repeat yourself, слабое связывание и инкапсуляция зависимостей, упрощение настройки...
На примерах? Всё начиналось как проект а-ля meteoClock, вот, что нужно, чтобы в прошивке Алекса заменить скажем заменить дисплей на другой тип? по-сути это пересборка проекта с нуля. Выделив части проекта в "процессы" позволяет заменять модули вообще никак не смешивая их. Так, например, в своем рабочем проекте я могу даже сделать чтото вроде конструктора а-ля "какие датчики будете использовать", чего в спагетти сделать никак нельзя. Есть возможность создания "библиотеки" процессов работающих с разными девайсами, а в прошивку вставлять почти не дорабатывая.

На мой взгляд, большинство подобных начинаний, во-первых, редко дорастают до чето-то серьезного, и во-вторых, всегда отдают некой кустарщиной.
Собственно мой вопрос - как надо расти так, чтобы не отдавало кустарщиной?))
 
  • Лойс +1
Реакции: Lumenjer

bort707

★★★★★★✩
21 Сен 2020
2,902
863
Собственно мой вопрос - как надо расти так, чтобы не отдавало кустарщиной?))
знал бы - сам писал фреймворки :)

Честно говоря, на мой взгляд вские системы улучшения чужого кода - вещь почти 100% бесполезная. Если вы пишете библиотеку или фреймворк, он должен решать конкретную задачу, скажем работу с каким-то железом или реализацию конкретного протокола. Тогда, есть надежда, этим кто-то будет пользоваться.
А система написания более красивого кода - это как некий инструмент, помогающий художнику писать "более удачные картины". Никакой уважающий себя художник не опустится до использования подобных ремесленных инструментов :)
 
  • Лойс +1
Реакции: Jumangee

Jumangee

✩✩✩✩✩✩✩
14 Дек 2020
11
3
Так-то оно так... переваривая своё же предыдущее сообщение подумалось, что в целом, неплохой наверное был бы вариант сделать действительно некий конфигуратор, набираешь девайсов из "библитеки" с которыми надо работать в твоей прошивке, какие-то настройки и всё такое, а на выходе - шаблон который уже "чуть-чуть"))) напильником доработать. Тогда, наверное, был бы смысл для тех, кто хоть немного понимает уже (совсем новичкам конечно рановато в ООП) и им нужен "быстрый старт"... а? Может в эту сторону попробовать?