В прошлой статье про написание конечных автоматов я обещал упаковать наш гениальный код в виде класса на C++ для повторного удобного использования. Делать буду так же на примере своей старой разработки SmartButton. Итак, влезаем в непонятный мир ардуининых библиотек и ООП.
Зачем всё это нужно?
Arduino IDE позволяет использовать синтаксис C++11, оказывается. То есть, там очень развитый объектно-ориентированный язык. Нам же хочется сосредотачиваться на нашем гениальном коде и размазанная по программе лишняя логика частенько мешает сосредоточиться. Взять, например, всякие дисплейчики, кнопочки, датчики и релюшки — у каждого же своя логика, зачем её смешивать с общей логикой программы. Тот же, например, дисплей. У него много полей, статических и изменяемых. Ой, поле — это же класс. Поле может входить в меню (класс меню) или нет, быть часть частью виртуального дисплея (класс), которых на физическом эеране может быть насколько (дисплеи: рабочий, настроек, диагностики и т.п.). Меню, в свою очередь, управляется кнопками (классы кнопок могут быть разными) или джойстиком (класс). Всё это вместе — класс "дисплей", который можно объявить в своей программе как:
#include "Display.h"
Display disp(куча параметров и настроек);
Если вы делаете проект не совсем на коленке не кое как и собираетесь что-то потом менять или повторно использовать какие-то свои наработки — лучше оформить сделанное в виде библиотек Arduino. В идеале, конечно же, положить в Github для других людей, если вам не жалко и вы не против, что кто-то ваш код исправит или дополнит.
Раз уж мы в прошлой статье делали кнопочку, давайте её оформим как класс и библиотеку?
Итак, наша задача сделать так, чтобы мы могли в своих скетчах писать:
#include "myButton.h"
myButton b1(4),b2(5),b3(12); // три кнопки на пинах 4, 5 и 12.
loop() {
b1.run();
b2.run();
b3.run();
// ...
if (b1.clicked()) doSomething(); // так или другим каким способом, есть варианты.
// ...
}
Как сделать библиотеку Arduino?
Это просто!
Сначала надо решить, как ваша библиотека будет называться. Пусть для примера, это будет MyLib.
Найдите, где лежат ваши скетчи на компьютере. Они лежат каждый в своей папочке, а рядом с ними есть папка libraries (библиотеки). Например, на маке /Users/Пользователь/Documents/Arduino/libraries и на виндоусе c:\Users\Пользователь\Документы\Arduino\libraries. Я сам сижу на маке и пути в виндах не знаю. Найдёте.
Вот в этой папке libraries создайте новую папку MyLib, то есть с именем своей библиотеки. Перейдите туда.
В этой новой папке надо создать как минимум один файл MyLib.h, тот, что вы будет включать в ваш проект. Минимальное его содержимое выглядит примерно так:
#ifndef MYLIB_H
#define MYLIB_H
#if ARDUINO >= 100
#include <Arduino.h>
#else
#include <WProgram.h>
#endif
// Ваш код здесь
#endif
Расскажу, что здесь зачем. Конструкция ниже позволяет включать вашу библиотеку в код несколько раз без ошибок. Лучше использовать название вашей библиотеки большими буквами. Это не сурово прямо обязательно, но все так делают и вы не выделяйтесь. Задача стоит придумать уникальное слово, в нашем случае MYLIB_H, идентификатор для этого заголовочного файла.
#ifndef MYLIB_H
#define MYLIB_H
// Ваш код
#endif
То есть, в вашем скетче может оказаться несколько таких строк:
#include "MyLib.h"
Вы скажете "тю, да я, да я слежу, да я..." и будете неправы. Лучше один раз написать в одном файле вот такую конструкцию, чем исправлять ваши готовые скетчи, если вдруг вы захотите вложить один в другой или ваша библиотека будет включена в другую итд. Данный код проверяет, определено ли слово MYLIB_H, если нет, то определяет его и включает дальнейший код. Если же слово уже определено, то второй раз код компилировать не нужно.
Следующий важный кусок кода:
#if ARDUINO >= 100
#include <Arduino.h>
#else
#include <WProgram.h>
#endif
Включает определения из исполняющей системы Arduino UDE. Без этого ваша библиотека просто не скомпилируется.
Всё. Закройте Arduino IDE, Откройте заново. Создайте новый скетч, пропишите там #include "MyLib.h" и ура, ваша библиотека есть и подключена!
Я смотрел, в библиотеке вроде как много файлов должно быть?
Да, конечно. Мы сделали минимальные действия, чтобы создать библиотеку. Теперь настало время планирования.
Чтобы я мог помещать сюда куски своего кода копипастом, я назову библиотеку SmartButton, ладно? Болванку MyLib можно прибить за ненадобностью.
По аналогии с предыдущим пунктом, создаём папку SmartButton, в ней:
- SmartButton.h — То, что мы будем включать в наши программы. Там будут только определения, без кода.
- SmartButton.cpp — Программный код класса. Это не скетч! Обратите внимание, что расширение файла cpp (C++).
- README.md — Файл описания библиотеки "для людей", то есть, документация. "md" означает MarkDown, то есть с разметкой. Достаточно назвать просто README.
- library.json — описание библиотеки для Arduino IDE в хитром формате JSON.
- examples — папка с примерами, которые будут потом видны в Arduino IDE. В ней должны лежать папки с именами примеров, в а них с тем же именем файлы с расширением ino — скетчи.
SmartButton.h
#ifndef SMART_BUTTON_H
#define SMART_BUTTON_H
#if ARDUINO >= 100
#include <Arduino.h>
#else
#include <WProgram.h>
#endif
// Можно выше до include переопределить эти значения
#ifndef SmartButton_debounce
#define SmartButton_debounce 10
#endif
#ifndef SmartButton_hold
#define SmartButton_hold 1000
#endif
#ifndef SmartButton_long
#define SmartButton_long 5000
#endif
#ifndef SmartButton_idle
#define SmartButton_idle 10000
#endif
class SmartButton {
// Это внутренние переменный класса.
// Они свои у каждого объекта и конфликта
// за имена переменных не будет.
// не надо выдумывать для каждой кнопки свои названия.
private:
byte btPin;
// Точно, как мы делали в [предыдущей статье про МКА](https://habrahabr.ru/post/345960/)
enum state {Idle, PreClick, Click, Hold, LongHold, ForcedIdle};
enum input {Press, Release, WaitDebounce, WaitHold, WaitLongHold, WaitIdle};
enum state btState = Idle;
enum input btInput = Release;
unsigned long pressTimeStamp;
// Это скрытый метод, его снаружи не видно.
private:
void DoAction(enum input in);
// Это то, чем можно пользоваться.
public:
// Конструкторы и деструкторы.
// То есть то, что создаёт и убивает объект.
SmartButton();
SmartButton(int pin);
SmartButton(int pin, int mode) {btPin=pin; pinMode(pin,mode);}
~SmartButton();
// В стиле Arduino IDE определим метод begin
void begin(int p, int m) {btPin=p; pinMode(p,m);}
// Генератор событий для помещения в loop().
void run();
// Методы для переопределения пользователем.
public:
inline virtual void onClick() {}; // On click.
inline virtual void onHold() {}; // On hold.
inline virtual void onLongHold() {}; // On long hold.
inline virtual void onIdle() {}; // On timeout with too long key pressing.
inline virtual void offClick() {}; // On depress after click.
inline virtual void offHold() {}; // On depress after hold.
inline virtual void offLongHold() {}; // On depress after long hold.
inline virtual void offIdle() {}; // On depress after too long key pressing.
};
#endif
Давайте поясню суть затеи. Мы не знаем, что нам будет нужно от кнопки. Наш МКА умеет находиться в состояниях Клик, Нажатие, Удержание и СлишкомДолгоеУдержание, а так же выходить из этих состояний в состояние Выключен. Так как мы делаем библиотеку универсальную, то надо предоставить возможность другому программисту вставить свой код в обработчики состояний. В ООП есть для этого замечательное средство — наследование.
Мы делаем класс, у которого есть несколько методов (функций) и они пустые. То есть, они есть, они будут вызываться в нужный момент, но кода в них нет. Зачем это? Затем, что в скетче можно будет создать свой класс на базе нашего, определить там только нужные из методов и наполнить их своим кодом.
Например, мы захотим сделать кнопку-переключатель, то есть, одно нажатие — включено, другое — выключено. Будем зажигать и гасить светодиод и предоставим функцию isOn() для использования в классическом виде в функции loop().
#include "SmartButton.h"
#define LED_PIN (13)
// Порождаем наш новый класс от SmartButton
class Toggle : public SmartButton {
private:
byte sw = 0; // состояние переключателя
byte led; // нога для лампочки
public:
Toggle(byte bt_pin, byte led_pin) : SmartButton(bt_pin) { // конструктор.
led=led_pin;
};
// Наши методы
// Включена кнопка или нет.
byte isOn() { return sw; }
// Что делать на клик.
virtual void onClick() {
if (sw) {
// Был включен. Выключаем.
digitalWrite(led,LOW);
// Здесь может быть любой ваш код на выключение кнопки.
} else {
// Был выключен. Включаем.
digitalWrite(led,HIGH);
// Здесь может быть любой ваш код на включение кнопки.
}
sw=!sw; // Переключаем состояние.
}
};
// Объявляем переменную bt нашего нового класса. Можно не одну.
Toggle bt(4,LED_PIN); // Нога 4, встроенный светодиод.
Toggle drill(12,8) // Нога 12, светодиод на ноге 8.
void loop() {
bt.run();
drill.run();
if (bt.isOn()) {
// что-то делать
} else {
// что-то другое делать
}
if (drill.isOn()) {
// что-то делать
} else {
// что-то другое делать
}
}
Как видите, нас совершенно здесь не интересует МКА кнопочки из предыдущей статьи, кода этой кнопки нет, он спрятан. Мы добавили свою функциональность к базовому классу и сделали переключатель по клику. Наш новый класс Toggle тоже можно оформить в виде библиотеки, кстати или положить в отдельный файл Toggle.h рядом с вашим скетчем, вам достаточно будет его подключить директивой #include. Мы так же задаём ногу со светодиодом для подсветки кнопки. Обратите внимание, что мы просто создали два объекта (bt и drill) нового класса Toggle, а МКА обработки кнопки для нас скрыт и не заботит.
Основываясь на классе SmartButton можно сделать свои классы, что понимают двойной клик, например, водят курсор по меню или поворачивают пулемётную турель медленно-быстрее в зависимости от времени удержания кнопки. Для этого достаточно определить свои методы, описанные в SmartButton.h как virtual. Все определять не обязательно, только нужные вам.
По просьбе целевой аудитории, вот пример класса PressButton, который предоставляет методы:
- pressed() — кнопка была нажата, можно вызывать много раз.
- ok() — я понял, слушай кнопку дальше, то есть сброс.
#include "SmartButton.h"
#define LED_PIN (13)
// Порождаем наш новый класс от SmartButton
class PressButton : public SmartButton {
private:
byte sw = 0; // состояние переключателя
public:
PressButton(byte bt_pin) : SmartButton(bt_pin) {}; // конструктор.
// Наши методы
// Была кликнута кнопка или нет.
byte pressed() { return sw; };
// Я всё понял, слушаем кнопку дальше.
void ok() { sw=0; };
// Что делать на клик.
virtual void onClick() { sw=1; };
};
// Объявляем переменную bt нашего нового класса. Можно не одну.
PressButton bt(4); // Нога 4.
PressButton drill(12) // Нога 12.
void loop() {
bt.run();
drill.run();
if (bt.pressed()) {
// что-то делать
bt.ok();
} else {
// что-то другое делать
}
if (drill.pressed()) {
// что-то делать
if (какое_то_условие) drill.ok();
} else {
// что-то другое делать
}
}
Таким образом мы получаем две независимо работающие "залипающие" кнопки, которые после нажатия находятся в состоянии pressed пока их не сбросить методом ok().
Если у вас есть меню, вы можете определить методы onClick() у кнопок "вверх" и "вниз", которые будут вызывать перемещение курсора меню на дисплее с соответствующем направлении. Определение onHold() у них может вызывать перемещение курсора в начало и конец меню, например. У кнопки "ентер" можно определить onClick() как выбор меню, onHold() как выход с сохранением, а onLongHold() как выход без сохранения.
Если вам нужен двойной клик, ну, определите onClick так, чтобы у вас там был счётчик нажатий и время с предыдущего нажатия. Тогда вы сможете различать одинарный и двойной клик.
SmartButton — это просто МКА, это инструмент для реализации поведения ваших кнопок.
Где же скрыта вся магия? Магия кроется в файле SmartButton.cpp
#include "SmartButton.h"
// Конструктор и деструктор пустые.
SmartButton::SmartButton() {}
SmartButton::~SmartButton() {}
// Конструктор с инициализацией.
// Он используется чаще всего.
SmartButton::SmartButton(int pin) {
btPin = pin;
pinMode(pin, INPUT_PULLUP);
}
// Машина конечных автоматов сидит здесь:
// Обратите внимание - это ровно та же функция,
// Что мы писали в [прошлой статье](https://habrahabr.ru/post/345960/).
// Обратите внимание на вызов виртуальных функций on* и off*.
void SmartButton::DoAction(enum input in) {
enum state st=btState;
switch (in) {
case Release:
btState=Idle;
switch (st) {
case Click:
offClick();
break;
case Hold:
offHold();
break;
case LongHold:
offLongHold();
break;
case ForcedIdle:
onIdle();
break;
}
break;
case WaitDebounce:
switch (st) {
case PreClick:
btState=Click;
onClick();
break;
}
break;
case WaitHold:
switch (st) {
case Click:
btState=Hold;
onHold();
break;
}
break;
case WaitLongHold:
switch (st) {
case Hold:
btState=LongHold;
onLongHold();
break;
}
break;
case WaitIdle:
switch (st) {
case LongHold:
btState=ForcedIdle;
break;
}
break;
case Press:
switch (st) {
case Idle:
pressTimeStamp=millis();
btState=PreClick;
break;
}
break;
}
}
// А это наш генератор событий.
// Его надо помещать в loop()
void SmartButton::run() {
unsigned long mls = millis();
if (!digitalRead(btPin)) DoAction(Press);
else DoAction(Release);
if (mls - pressTimeStamp > SmartButton_debounce) DoAction(WaitDebounce);
if (mls - pressTimeStamp > SmartButton_hold) DoAction(WaitHold);
if (mls - pressTimeStamp > SmartButton_long) DoAction(WaitLongHold);
if (mls - pressTimeStamp > SmartButton_idle) DoAction(WaitIdle);
}
Логика местами спорная, я знаю :) Но это работает.
Теперь осталось заполнить файл README описанием вашей библиотеки и заполнить по аналогии файлик library.json, где поля вполне очевидны:
{
"name": "SmartButton",
"keywords": "button, abstract class, oop",
"description": "The SmartButton abstract class for using custom buttons in Arduino sketches.",
"repository": {
"type": "git",
"url": "https://github.com/nw-wind/SmartButton"
},
"version": "1.0.0",
"authors": {
"name": "Sergei Keler",
"url": "https://github.com/nw-wind"
},
"frameworks": "arduino",
"platforms": "*"
}
Если у вас нет репозитория, можно эту секцию не указывать.
Ура! Библиотека готова. Можно запаковать папку в ZIP и раздавать друзьям или копировать на другие свои компьютеры.
По аналогии, можно сделать класс для любой МКА. Принцип общий: вы делаете класс, определяете виртуальные методы, которые потом надо будет переопределить, чтобы вставить свой код или готовые методы, если универсальность не требуется.
Что за Github и зачем он мне?
Github — это огромное сообщество программистов. Да, ваш код будет публично светиться на весь интернет, но… любой человек может предложить свои правки к вашему коду. Мне, например, очень помогли с SmartDelay два человека, один из которых сделал свою подобную библиотеку и мы поподсматривали чуть-чуть код друг у друга. Лучше две хорошие библиотеки, чем две глюкавые, правда?
Чтобы поместить вашу библиотеку в Github надо сделать там аккаунт, сгенерить ключ и создать репозиторий с там же именем, что ваша библиотека (папка). Файлы можно загрузить через web-шнтерфейс.
Для установки библиотеки из Github в Arduino IDE достаточно скопировать URL и воспользоваться утилитой git:
Или загрузить ZIP — это будет как раз библиотека Arduino, как и все прочие библиотеки.
Как пользоваться git вообще и Github в частности, есть много статей наверняка. Попробуйте поискать. Если не найдёте, я напишу как им пользуюсь я.
4eyes
Круто, всё собирался написать что-нибудь подобное в общем виде для себя.
Один вопрос: virtual-методы заставляют компилятор всунуть в класс указателя на vtable, в конструктор — инициализацию указателя и еще куда-нибудь — саму vtable.
Вы не думали над тем, чтобы реализвовать полиморфизм времени компиляции с помощью CRTP? Результат будет тот же, размер объекта меньше на 1 указатель.
nwwind Автор
Я лишь описал очевидный подход. Глубоко не копал. Надеюсь, компилятор меня понял и сунул всё по максимуму на флеш :)
Когда начинаешь считать байты, вся красота сходит и получается голый Си и портянка кода в 50 страниц одним куском.
Например, состояния кнопки, можно впихнуть по две кнопки в байт, а верхнего уровня класс по 8 кнопок на байт (по биту) может держать.
Вся эта объектность явно не экономит память, но позволяет быстро слепить и получить лаконичный понятный код.
В примере с кнопкой, я бы предположил, что компилятор сделает ранее связывание и не будет никаких лишних указателей. Можно проверить сделав sizeof у объекта.
Проверил — размер PressButton ровно на 1 байт больше (byte sw), чем у родительского SmartButton.
Беда начинается, если бы были указатели на объекты.
4eyes
Интрига: а почему 11?
Пруф того, что enum и указатель — 2 байта в коде выглядит так: create.arduino.cc/editor/4eyes/dcbd34bd-ff67-428a-b67e-116273eae6f2/preview
nwwind Автор
Да-да. Вкурил, спасибо.
Утоптал в 6 байтов в итоге, уже лучше.
nwwind Автор
Размер SmartButton 11 байт, унаследованного PressButton 12.
nwwind Автор
Я думал про шаблон, да. Я пока не настолько владею плюсами. Я закончил кодить, когда они только появились и были странным глумлением над С.
Мне не нравится, например, что надо конструкторы у нового класса надо писать. Лучше б сгенерить.
CRTP не даст ли доступ к приватным переменным порождённому классу? Это было бы плохо и создаёт потенциальные проблемы.
4eyes
Я не совсем понял, что имеется в виду под «писать».
Если «реализовать» — то это не обязательно, до тех пор пока вас устраивает дефолтная реализация. Дефолтная реализация конструктора по умолчанию не делает ничего, а копирования — вызывает конструктор копирования родителя, а потом конструкторы копирования всех членов класса. Для POD типов (int, float, ..., и struct без конструкторов) — читай копирует побайтно.
nwwind Автор
Вот это:
olegator99
Что бы подтянуть в наследник конструкторы базового класса можно написать так:
nwwind Автор
О! Это в каком месте? внутри конструктора наследуемого класса или вместо него?
class newclass: public baseclass {
using baseclass;
или
newclass() { using baseclass; }
olegator99
вот так:
nwwind Автор
Спасибо, попробую. Эх, отстал от моды я на 30 лет…
nwwind Автор
Надо покурить это на досуге, спасибо.
4eyes
В кратце, это удобно для того, чтобы реализовать часть поведения в наследнике, а часть — в базовом классе.
Подробнее хорошо описано тут: isocpp.org/wiki/faq/strange-inheritance#private-virtuals
Можно сделать так:
nwwind Автор
Это позднее связывание. Фуфу. Не наш метод. Оно как раз подъедает по 4 байта в ардуине на указатель. :)
Если можно обойтись ранним, лучше им.
nwwind Автор
Наотимизировал SmartButton и он теперь 6 байт жрёт.
причём 2 забирает слово virtual :( причём один раз и путь лучше в базовом классе.
Amomum
Вы как-то очень странно расставляете отступы для препроцессорных директив. Если я правильно помню стандарт, отступ перед # вообще ставить нельзя (хотя все известные мне компиляторы на это внимания не обращают). Но почему у вас написано:
Почему у #define MYLIB_H есть отступ, а у всего остального нет?
К слову, раз уж взяли С++11, используйте enum class, а не просто enum. Это сильно снижает количество глупых ошибок.
nwwind Автор
На гитхабе уже enum class
Здесь чем проще, тем лучше. Для начинающих.
Отступ, хмм… не так принципиально ведь?
Amomum
Разумеется, не принципиально, просто странно.
Главное — не доводить до такого
nwwind Автор
ща проморгаюсь от глазного… уффуфууу.
nwwind Автор
Про прагму ванс слышал, но так как-то привычнее, ну старорежимный я, и спокойнее.
Я использовал
#ifndef A
#define A
//
#endif
ещё в 1985 году, когда только Цэ у нас появился.
Привычка.
Amomum
После третьей совершенно мистической ошибки компиляции, которая была вызвана копированием файла без исправления стража включения, я понял, что прагма рулит. И писать меньше и править не надо, если файл переименовывается. И опять-таки ошибиться почти невозможно.
Остаются, конечно, некоторые сложные случаи, когда она не работает, но они редки.
Но дело ваше :)
nwwind Автор
Тут рекомендуют и то и то. Оно друг другу не мешает, оказывается.
Задумался.
MrGobus
Вот тут думаю стоило бы развернуть:
Цитата из robocraft.ru/blog/arduino/751.html
nwwind Автор
Согласен.
c4boomb
Антипаттерн на антипаттерне. Почему ваша Toggle кнопка, знает то что она делает (переключает лед).
Класс кнопки должен описывать только изменение её состояния. А то захочется вам сделать тугл но не для леда и будете городить новый класс
nwwind Автор
так и есть. городить новый. в это суть подхода. что в этом страшного?
c4boomb
в том что зачем если можно использовать классический Event-Observer в котором кнопка тригерит ивент, а обсервер по "идентфикатору кнопки" выполняет действия. Тогда вы разграничиваете логику и у вас 1 класс тугл.
SOLID
c4boomb
У вас нарушается и прицип инверсии зависимостей, и принцип единственной ответственности
nwwind Автор
Надо почитать хоть что это.
Я не программист. Про бульдозер я загнул, конечно, но я закончил с программированием промышленным лет 30 назад. Я ничего не понял про евент-обсервер и нижеследующие принципы.
Нет ли желания показать на примере с тоглом и евент-обсервером?
olegator99
Наверно имеется ввиду примерно такая конструкция:
Это с использованием виртуальных функций. Можно с шаблонами, код будет посложнее, однако удастся избежать лишнего указателя на vtable и убрать интерфейс observer. Можно так и так, но первый вариант на мой вкус удобочтимее.
nwwind Автор
Ой, отсыпь…
Это для меня, увы, слишком сложно. Я в такие дебри ещё не лазал.
На буднях кодеров попрошу пояснить, что это такое. :)
4eyes
Мне кажется, можно проще. Прошу прощения, ардуины под рукой нет, проверить не могу, но вот синтаксически верный набросок на С++:
godbolt.org/g/yy3TWp
c4boomb
К сожалению, я не силен в C++.
1) Ивент-обсервер:
У вас есть события и обработчики. Событие к примеру "нажатие кнопки", "правый клик мышкой" и тд и тп
Обработчик это объект который "слушает"(ждет) событие и когда оно происходит выполняет какие-то действия
В этом случае ваша кнопка не знает что будет происходить по её нажатию. Условно можете это представлять как реальную кнопку, которая не знает, что будет происходить в цепи когда на неё нажмут, она лишь замыкает или размыкает контакт.
По аналогии вы создали кнопку, которая может зажигать только светодиод. В реальном мире это выглядело бы как кнопка связанная с светодиодом, которая не может быть использованна ни с чем кроме светодиода. Не логично, лучше создать универсальную кнопку)
Чем больше ваши классы связанны между собой тем сложнее будет расширять (масштабировать) систему в будущем.
nwwind Автор
Я в примере сделал класс «кнопка toggle с подсветкой светодиодом». Это новый вид кнопки.
nwwind Автор
Универсальность да, хочется, согласен.
Покурю про обсерверы ещё.
c4boomb
А по поводу SOLID.
Это 5 принципов, следуя которым, вы в 90% случаев напишите хороший код, который вы сможете легко тестировать, масштабировать и поддерживать в будущем
nwwind Автор
Да вроде как старался следовать…
Tarik02
«inline virtual»?
nwwind Автор
Звучит странно, но работает. На откуп компилятору.
a-tk
Совсем не так. Там полноценный С++, но из среды выполнения выпилили исключения и нет всяких STL, ибо на эмбеддед мало памяти и вообще динамическая аллокация — зло.
Надо будет набросать статейку про тюнинг ардуиновского тулчейна…
nwwind Автор
Давай! Буде статья — дай знать!
a-tk
Накорябал, жду модерации
Alex_ME
Оффтоп про Arduino-библиотеки.
Пытался класть библиотеку рядом со скетчем и инклюдить через относительный пути
и получал ошибки линкера на функции и пр. из библиотеки.
Можно как-нибудь библиотеки локально хранить, чтобы потом тем, кто этот код будет использовать не требовалось их вручную ставить?
nwwind Автор
Если положить файлы рядом, они автоматом скомпилятся тоже.
Я же написал в статье, куда положить и как, чтобы IDE увидел их как библиотеки. Не надо писать пути в include. Надо выполнить эти условия, а не изобретать велосипед.
Чтобы кто-то мог использовать этот код — его лучше держать на гитхабе. Да, включая сами скетчи тоже.
Alex_ME
Да я об этом и веду речь.
Есть некая библиотека AAA. Я ее скачал с гитхаба или через встроенный менеджер скачал — так или иначе она будет доступна по пути "C:...\Arduino\Libraries\AAA".
Затем я заливаю скетч на GitHub, и мне придется указать в ReadMe, что пользователь должен скачать библиотеку AAA и корректно ее расположить, чтобы IDE подхватила. Это довольно неудобно и для меня, и, особенно, для пользователя. В случае, если библиотеки расположены в папке скетча, достаточно клонировать репозиторий и все сразу собирается.
Альтернативным решением, принятым в цивилизованном мире, является использование менеджеров зависимостей (привет NuGet, npm, pip и т.д), чтобы прописать зависимости проекта и они потом автоматически подгрузились. Но, увы, среда Arduino не предоставляет таких возможностей.
Wilk
Здравствуйте!
Я тоже в какой-то момент столкнулся с такой проблемой. Насколько я понимаю, фактически это нарушает всю идею заголовочных файлов. Причина этого в том, как Arduino IDE (если это поделие можно называть IDE) производит обработку файлов. Повозившись немного, понял, что лучшим решением проблемы является переход на использование инструментов, разработанных для разработчиков, а не для домохозаяек.
Лучшим, что нашёл, является platform.io. Порадовало наличие возможности производить тестирование кода, организация сборки под разные платформы, наличие работающей системы зависимостей (в Arduino IDE есть выкидышь на эту тему, целый пакетный менеджер, который не позволяет задавать зависимости проекта, в результате чего периодически сборка чужого проекта превращается в игру «угадай версию библиотеки, использованной разработчиком»). На данный момент меня полностью устраивает. В случае, если будете пользоваться и посмотрите в сторону IDE, рекомендую посмотреть на версию, основанную на VS Code. Как минимум на моём не молодом ноутбуке данная версию работала куда как бодрее, чем выкидышь на Atom.
nwwind Автор
Начинают же домохозяйки :) и для них Arduino IDE прекрасно во всём :)
Platformio поставил, смотрю. Прикольно. Примерно всё то же самое. Редактор (Atom) такой же неудобный. Меня бы больше устроил из командной строки make, в редактор есть Sublime :)
Посмотрю на вс-коре
Wilk
Здравствуйте!
Главная прелесть planform.io в том, что его можно запускать из командной строки. При этом можно запускать как сборку всех целей (под разные платы, если есть), так и под конкретную. Единственное, что мне показалось не совсем удобным, это способ задания последовательного порта. Но это может быть причиной поверхностного ознакомления с документацией.
nwwind Автор
С vs code намного лучше, хмм…
С гитом дружит, ооочень хорошо это.
Я такой минус нашёл:
Пока явно не указал #include <Wire.h> — не собирался. Подозреваю, что в ssd1306.h вместо <> стоят кавычки :)
Alex_ME
Спасибо за ценный совет.
Вспомнил, пользовался platform.io как-то год назад, (правда, на атоме и лагало сильно), но довольно понравилось. Забыл, вот.
Я так-то редко пишу именно под Arduino, а эту свистопляску с библиотеками обнаружил, когда один человек попросил написать для него небольшую вещь. И чтобы его не утруждать, хотел скинуть все библиотеки в проект, чтобы обойтись одним
git clone
, условно.Даже не знаю, какая инструкция проще:
nwwind Автор
Platformio пока нравится. Играю вот.