diff --git a/src/modules/display/U8g2lib.zip b/src/modules/display/U8g2lib.zip new file mode 100644 index 00000000..aacb2535 Binary files /dev/null and b/src/modules/display/U8g2lib.zip differ diff --git a/src/modules/display/U8g2lib/DisplayTypes.h b/src/modules/display/U8g2lib/DisplayTypes.h new file mode 100644 index 00000000..4288dc75 --- /dev/null +++ b/src/modules/display/U8g2lib/DisplayTypes.h @@ -0,0 +1,631 @@ +#pragma once +#include "Global.h" +#include +#include +#include + +// #define DEBUG_DISPLAY + +#define DEFAULT_PAGE_UPDATE_ms 500 +// #define DEFAULT_PAGE_TIME_ms 5000 +// #define DEFAULT_ROTATION 0 +// #define DEFAULT_CONTRAST 10 +#define MIN_CONTRAST 10 +#define MAX_CONTRAST 150 + +#ifndef DEBUG_DISPLAY +#define D_LOG(fmt, ...) \ + do { \ + (void)0; \ + } while (0) +#else +#define D_LOG(fmt, ...) Serial.printf((PGM_P)PSTR(fmt), ##__VA_ARGS__) +#endif + +enum rotation_t : uint8_t { + ROTATION_NONE, + ROTATION_90, + ROTATION_180, + ROTATION_270 +}; + +uint8_t parse_contrast(int val) { + if (val < MIN_CONTRAST) val = MIN_CONTRAST; + if (val > MAX_CONTRAST) val = MAX_CONTRAST; + return val; +}; + +rotation_t parse_rotation(int val) { + if ((val > 0) && (val <= 90)) return ROTATION_90; + if ((val > 90) && (val <= 180)) return ROTATION_180; + if ((val > 180) && (val <= 270)) return ROTATION_270; + return ROTATION_NONE; +}; + +struct DisplayPage { + String key; + uint16_t time; + rotation_t rotate; + String font; + String format; + String valign; + + DisplayPage( + const String& key, + uint16_t time, + rotation_t rotate, + const String& font, + const String& format, + const String& valign) : key{key}, time{time}, rotate{rotate}, font{font}, format{format}, valign{valign} {} + + // void load(const JsonObject& obj) { + // // time = obj["time"].as(); + // // rotate = parse_rotation(obj["rotate"].as()); + // // font = obj["font"].as(); + // // valign = obj["valign"].as(); + // // format = obj["format"].as(); + // } + + // auto item = DisplayPage( pageObj["key"].as(), _update, _rotate, _font); + // // Загрузка настроек страницы + // item.load(pageObj); + // page.push_back(item); + + +}; + +enum position_t { + POS_AUTO, + POS_ABSOLUTE, + POS_RELATIVE, + POS_TEXT +}; + +struct RelativePosition { + float x; + float y; +}; + +struct TextPosition { + uint8_t row; + uint8_t col; +}; + +struct Point { + uint16_t x; + uint16_t y; + + Point() : Point(0, 0) {} + + Point(uint16_t x, uint16_t y) : x{x}, y{y} {} + + Point(const Point& rhv) : Point(rhv.x, rhv.y) {} +}; + +struct Position { + position_t type; + union { + Point abs; + RelativePosition rel; + TextPosition text; + }; + + Position() : type{POS_AUTO} {} + + Position(const Point& pos) : type{POS_ABSOLUTE} { + abs.x = pos.x; + abs.y = pos.y; + } + + Position(const RelativePosition& pos) : type{POS_RELATIVE} { + rel.x = pos.x; + rel.y = pos.y; + } + + Position(const TextPosition& pos) : type{POS_TEXT} { + text.col = pos.col; + text.row = pos.row; + } + + Position(const Position& rhv) : type{rhv.type} { + switch (type) { + case POS_ABSOLUTE: + abs = rhv.abs; + case POS_RELATIVE: + rel = rhv.rel; + case POS_TEXT: + text = rhv.text; + default: + break; + } + } +}; + +class Cursor : public Printable { + private: + Point _size; + + public: + TextPosition pos{0, 0}; + Point abs{0, 0}; + Point chr; + Cursor(){}; + + Cursor(const Point& size, const Point& chr) : _size{size}, chr{chr} { + D_LOG("w: %d, h: %d, ch: %d(%d)\r\n", _size.x, _size.y, chr.x, chr.y); + } + + void reset() { + pos.col = 0; + pos.row = 0; + abs.x = 0; + abs.y = 0; + } + + void lineFeed() { + pos.col = 0; + pos.row++; + abs.x = 0; + abs.y += chr.y; + } + + void moveX(uint8_t x) { + abs.x += x; + pos.col = abs.x / chr.x; + } + + void moveY(uint8_t y) { + abs.y += y; + } + + void moveXY(uint8_t x, uint8_t y) { + moveX(x); + moveY(y); + } + + void moveCarret(uint8_t col) { + pos.col += col; + moveX(col * chr.x); + } + + bool isEndOfPage(uint8_t rows = 1) { + return (abs.y + (rows * chr.y)) > _size.y; + } + + bool isEndOfLine(uint8_t cols = 1) { + return (abs.x + (cols * chr.x)) > _size.x; + } + + size_t printTo(Print& p) const { + return p.printf("(c:%d, r:%d x:%d, y:%d)", pos.col, pos.row, abs.x, abs.y); + } +}; + + +struct DisplayHardwareSettings { + int update = DEFAULT_PAGE_UPDATE_ms; + rotation_t rotate; + String font; + int pageTime; + String pageFormat; + int contrast; + bool autoPage; + String valign; +}; + +class Display { + private: + unsigned long _lastResfresh{0}; + Cursor _cursor; + U8G2 *_obj{nullptr}; + DisplayHardwareSettings *_settings; + + public: + Display(U8G2 *obj, DisplayHardwareSettings *settings) : _obj{obj}, _settings(settings) { + _obj->begin(); + _obj->enableUTF8Print(); + _obj->setContrast(_settings->contrast); + setFont(settings->font); + setRotation(settings->rotate); + clear(); + } + + ~Display () { + if (_obj) { + delete _obj; + _obj = nullptr; + } + } + + void setRotation(rotation_t rotate) { + switch (rotate) { + case ROTATION_NONE: + _obj->setDisplayRotation(U8G2_R0); + break; + case ROTATION_90: + _obj->setDisplayRotation(U8G2_R1); + break; + case ROTATION_180: + _obj->setDisplayRotation(U8G2_R2); + break; + case ROTATION_270: + _obj->setDisplayRotation(U8G2_R3); + break; + } + } + + void setFont(const String &fontName = "") { + if (fontName.isEmpty()) { + Display::setFont(_settings->font); + return; + } + + if (fontName.startsWith("c6x12")) + _obj->setFont(u8g2_font_6x12_t_cyrillic); + else if (fontName.startsWith("s6x12")) + _obj->setFont(u8g2_font_6x12_t_symbols); + + else if (fontName.startsWith("c6x13")) + _obj->setFont(u8g2_font_6x13_t_cyrillic); + + else if (fontName.startsWith("c7x13")) + _obj->setFont(u8g2_font_7x13_t_cyrillic); + else if (fontName.startsWith("s7x13")) + _obj->setFont(u8g2_font_7x13_t_symbols); + + else if (fontName.startsWith("c8x13")) + _obj->setFont(u8g2_font_8x13_t_cyrillic); + else if (fontName.startsWith("s8x13")) + _obj->setFont(u8g2_font_8x13_t_symbols); + + else if (fontName.startsWith("c9x15")) + _obj->setFont(u8g2_font_9x15_t_cyrillic); + else if (fontName.startsWith("s9x15")) + _obj->setFont(u8g2_font_9x15_t_symbols); + + else if (fontName.startsWith("c10x20")) + _obj->setFont(u8g2_font_10x20_t_cyrillic); + else if (fontName.startsWith("unifont")) + _obj->setFont(u8g2_font_unifont_t_symbols); + else if (fontName.startsWith("siji")) + _obj->setFont(u8g2_font_siji_t_6x10); + else + _obj->setFont(u8g2_font_6x12_t_cyrillic); + + _cursor.chr.x = getMaxCharHeight(); + // _cursor.chr.y = getLineHeight(); + } + + void initCursor() { + _cursor = Cursor( + {getWidth(), getHeight()}, + {getMaxCharHeight(), getLineHeight()}); + } + + void getPosition(const TextPosition &a, Point &b) { + b.x = a.col * _cursor.chr.x; + b.y = (a.row + 1) * _cursor.chr.y; + } + + void getPosition(const RelativePosition &a, Point &b) { + b.x = getHeight() * a.x; + b.y = getWidth() * a.y; + } + + void getPosition(const Point &a, TextPosition &b) { + b.row = a.y / getLineHeight(); + b.col = a.x / getMaxCharWidth(); + } + + void getPosition(const RelativePosition &a, TextPosition &b) { + Point tmp; + getPosition(a, tmp); + getPosition(tmp, b); + } + + void draw(const RelativePosition &pos, const String &str) { + Point tmp; + getPosition(pos, tmp); + draw(tmp, str); + } + + void draw(TextPosition &pos, const String &str) { + Point tmp; + getPosition(pos, tmp); + draw(tmp, str); + } + + Cursor *getCursor() { + return &_cursor; + } + + // print меняю cursor + void println(const String &str, bool frame = false) { + print(str, frame); + _cursor.lineFeed(); + } + + void print(const String &str, bool frame = false) { + //Serial.print(_cursor); + // x, y нижний левой + int width = _obj->drawUTF8(_cursor.abs.x, _cursor.abs.y + _cursor.chr.y, str.c_str()); + if (frame) { + int x = _cursor.abs.x - getXSpacer(); + int y = _cursor.abs.y - _cursor.chr.y; + width += (getXSpacer() * 2); + int height = _cursor.chr.y + getYSpacer() * 2; + // x, y верхней левой. длина, высота + _obj->drawFrame(x, y, width, height); + D_LOG("[x:%d y:%d w:%d h:%d]", x, y, width, height); + } + _cursor.moveX(width); + } + + // draw не меняет cursor + void draw(const Point &pos, const String &str) { + Serial.printf("(x:%d,y:%d) %s", pos.x, pos.y, str.c_str()); + _obj->drawStr(pos.x, pos.y, str.c_str()); + } + + uint8_t getLineHeight() { + return getMaxCharHeight() + getYSpacer(); + } + + int getXSpacer() { + int res = getWidth() / 100; + if (!res) res = 1; + return res; + } + + int getYSpacer() { + int res = (getHeight() - (getLines() * getMaxCharHeight())) / getLines(); + if (!res) res = 1; + return res; + } + + uint8_t getWidth() { + return _obj->getDisplayWidth(); + } + + uint8_t getHeight() { + return _obj->getDisplayHeight(); + } + + uint8_t getLines() { + uint8_t res = getHeight() / _obj->getMaxCharHeight(); + if (!res) res = 1; + return res; + } + + uint8_t getMaxCharHeight() { + return _obj->getMaxCharHeight(); + } + + uint8_t getMaxCharWidth() { + return _obj->getMaxCharWidth(); + } + + void clear() { + _obj->clearDisplay(); + _cursor.reset(); + } + + void startRefresh() { + _obj->clearBuffer(); + _cursor.reset(); + } + + void endRefresh() { + _obj->sendBuffer(); + _lastResfresh = millis(); + } + + bool isNeedsRefresh() { + // SerialPrint("[Display]", "_settings->update: " + String(_settings->update) + "ms", ""); + return !_lastResfresh || (millis() > (_lastResfresh + _settings->update)); + } +}; + +struct ParamPropeties { + // рамка + bool frame[false]; +}; + +struct Param { + // Ключ + const String key; + // Префикс к значению + String pref; + // Суффикс к значению + String suff; + // Значение + String value; + + String pref_fnt; + String suff_fnt; + String value_fnt; + + String gliphs; + + // значение изменилось + bool updated; + // группа + uint8_t group; + ParamPropeties props; + Position position; + + Param(const String &key, + const String &pref = emptyString, const String &value = emptyString, const String &suff = emptyString, + const String &pref_fnt = emptyString, const String &value_fnt = emptyString, const String &suff_fnt = emptyString, + const String &gliphs = emptyString + ) : key{key}, group{0} { + setValue(value.c_str()); + setPref(pref); + setSuff(suff); + this->pref_fnt = pref_fnt; + this->value_fnt = value_fnt; + this->suff_fnt = suff_fnt; + this->gliphs = gliphs; + updated = false; + } + + bool isValid() { + return !pref.isEmpty(); + } + + bool setPref(const String &str) { + if (!pref.equals(str)) { + pref = str; + updated = true; + return true; + } + return false; + } + + bool setSuff(const String &str) { + if (!suff.equals(str)) { + suff = str; + updated = true; + return true; + } + return false; + } + + bool setValue(const String &str) { + if (!value.equals(str)) { + value = str; + updated = true; + return true; + } + return false; + } + + + + void draw(Display *obj, uint8_t line) { + } + + void draw(Display *obj) { + auto type = position.type; + switch (type) { + case POS_AUTO: { + D_LOG("AUTO %s '%s%s'\r\n", key.c_str(), descr.c_str(), value.c_str()); + obj->setFont(pref_fnt); + obj->print(pref.c_str()); + + obj->setFont(value_fnt); + obj->println(value.c_str(), false); + + obj->setFont(suff_fnt); + obj->print(suff.c_str()); + } + case POS_ABSOLUTE: { + auto pos = position.abs; + D_LOG("ABS(%d, %d) %s %s'\r\n", pos.x, pos.y, key.c_str(), value.c_str()); + obj->draw(pos, value); + } + case POS_RELATIVE: { + auto pos = position.rel; + D_LOG("REL(%2.2f, %2.2f) %s %s'\r\n", pos.x, pos.y, key.c_str(), value.c_str()); + obj->draw(pos, value); + } + case POS_TEXT: { + auto pos = position.text; + D_LOG("TXT(%d, %d) %s %s'\r\n", pos.col, pos.row, key.c_str(), value.c_str()); + obj->draw(pos, value); + } + default: + D_LOG("unhadled: %d", type); + } + } +}; + +class ParamCollection { + std::vector _item; + + public: + void load() { + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) { + if ((*it)->getSubtype() == "" || (*it)->getSubtype() == "U8g2lib") continue; + + auto entry = find((*it)->getID()); + if (!entry) { + _item.push_back({(*it)->getID(), (*it)->getID() + ": ", (*it)->getValue(), "", "", "", ""}); + } else { + entry->setValue((*it)->getValue()); + if (entry->pref == "") + entry->setPref((*it)->getID() + ": "); + } + } + } + + void loadExtParamData(String parameters) { + String id = ""; + jsonRead(parameters, "id", id, false); + if (id != "") { + String pref = ""; + String suff = ""; + String pref_fnt = ""; + String suff_fnt = ""; + String value_fnt = ""; + String gliphs = ""; + + bool hasExtParam = false; + + hasExtParam = hasExtParam + jsonRead(parameters, "pref", pref, false); + hasExtParam = hasExtParam + jsonRead(parameters, "suff", suff, false); + hasExtParam = hasExtParam + jsonRead(parameters, "pref_fnt", pref_fnt, false); + hasExtParam = hasExtParam + jsonRead(parameters, "suff_fnt", suff_fnt, false); + hasExtParam = hasExtParam + jsonRead(parameters, "value_fnt", value_fnt, false); + hasExtParam = hasExtParam + jsonRead(parameters, "gliphs", gliphs, false); + + if (hasExtParam) { + _item.push_back({id, pref, "", suff, pref_fnt, value_fnt, suff_fnt, gliphs}); + } + } + } + + Param *find(const String &key) { + return find(key.c_str()); + } + + Param *find(const char *key) { + Param *res = nullptr; + for (size_t i = 0; i < _item.size(); i++) { + if (_item.at(i).key.equalsIgnoreCase(key)) { + res = &_item.at(i); + break; + } + } + return res; + } + + Param *get(int n) { + return &_item.at(n); + } + + size_t count() { + return _item.size(); + } + + // n - номер по порядку параметра + Param *getValid(int n) { + for (size_t i = 0; i < _item.size(); i++) + if (_item.at(i).isValid()) + if (!(n--)) return &_item.at(i); + return nullptr; + } + + size_t getVaildCount() { + size_t res = 0; + for (auto entry : _item) res += entry.isValid(); + return res; + } + + size_t max_group() { + size_t res = 0; + for (auto entry : _item) + if (res < entry.group) res = entry.group; + return res; + } +}; \ No newline at end of file diff --git a/src/modules/display/U8g2lib/U8g2lib.cpp b/src/modules/display/U8g2lib/U8g2lib.cpp new file mode 100644 index 00000000..97dc291f --- /dev/null +++ b/src/modules/display/U8g2lib/U8g2lib.cpp @@ -0,0 +1,419 @@ +#include "Global.h" +#include "classes/IoTItem.h" +#include +#include "DisplayTypes.h" + +#define STRHELPER(x) #x +#define TO_STRING_AUX(...) "" #__VA_ARGS__ +#define TO_STRING(x) TO_STRING_AUX(x) + + +// дополненный список параметров для вывода, который синхронизирован со списком значений IoTM +ParamCollection *extParams{nullptr}; + +// класс одного главного экземпляра экрана для выделения памяти только когда потребуется экран +class DisplayImplementation { + private: + unsigned long _lastPageChange{0}; + bool _pageChanged{false}; + // uint8_t _max_descr_width{0}; + // typedef std::vector Line; + // текущая + size_t _page_n{0}; + // struct Page { + // std::vector line; + // }; + + uint8_t _n{0}; // последний отображенный + + DisplayHardwareSettings *_context{nullptr}; + Display *_display{nullptr}; + + public: + DisplayImplementation(DisplayHardwareSettings *context = nullptr, + Display *display = nullptr) + : _context(context), _display(display) { + + } + + ~DisplayImplementation() { + if (_display) { + delete _display; + _display = nullptr; + } + if (_context) { + delete _context; + _context = nullptr; + } + if (extParams) { + delete extParams; + extParams = nullptr; + } + } + + std::vector page; + + void nextPage() { + _n = _n + 1; + if (_n == page.size()) _n = _n - 1; + _pageChanged = true; + } + + void prevPage() { + if (_n > 0) _n = _n - 1; + _pageChanged = true; + } + + void rotPage() { + _n = _n + 1; + if (_n == page.size()) _n = 0; + _pageChanged = true; + } + + void gotoPage(uint8_t num) { + _n = num; + if (num < 0) _n = 0; + if (num >= page.size()) _n = page.size() - 1; + _pageChanged = true; + } + + void setAutoPage(bool isAuto) { + if (_context) _context->autoPage = isAuto; + _pageChanged = true; + } + + uint8_t calcPageCount(ParamCollection *param, uint8_t linesPerPage) { + size_t res = 0; + size_t totalLines = param->count(); + if (totalLines && linesPerPage) { + res = totalLines / linesPerPage; + if (totalLines % linesPerPage) res++; + } + return res; + } + + // uint8_t getPageCount() { + // return isAutoPage() ? calcPageCount(_param, _display->getLines()) : getPageCount(); + // } + + // выводит на страницу параметры начиная c [n] + // возвращает [n] последнего уместившегося + uint8_t draw(Display *display, ParamCollection *param, uint8_t n) { + // Очищает буфер (не экран, а внутреннее представление) для последущего заполнения + display->startRefresh(); + size_t i = 0; + // вот тут лог ошибка + for (i = n; i < param->count(); i++) { + auto cursor = display->getCursor(); + auto entry = param->get(i); + auto len = entry->value.length() + entry->pref.length() + entry->suff.length() ; + if (cursor->isEndOfLine(len)) cursor->lineFeed(); + + printParam(display, entry, _context->font); + + if (cursor->isEndOfPage(0)) break; + } + // Отправит готовый буфер страницы на дисплей + display->endRefresh(); + return i; + } + + String slice(const String &str, size_t index, char delim) { + size_t cnt = 0; + int subIndex[] = {0, -1}; + size_t maxIndex = str.length() - 1; + + for (size_t i = 0; (i <= maxIndex) && (cnt <= index); i++) { + if ((str.charAt(i) == delim) || (i == maxIndex)) { + cnt++; + subIndex[0] = subIndex[1] + 1; + subIndex[1] = (i == maxIndex) ? i + 1 : i; + } + } + return cnt > index ? str.substring(subIndex[0], subIndex[1]) : emptyString; + } + + void printParam(Display *display, Param *param, const String &parentFont) { + if (!param->pref.isEmpty()) { + display->setFont(param->pref_fnt.isEmpty() ? parentFont : param->pref_fnt); + display->print(param->pref); + } + + if (!param->value.isEmpty()) { + display->setFont(param->value_fnt.isEmpty() ? parentFont : param->value_fnt); + if (!param->gliphs.isEmpty() && isDigitStr(param->value)) { + int glyphIndex = param->value.toInt(); + display->print(getUtf8CharByIndex(param->gliphs, glyphIndex)); + } else display->print(param->value); + } + + if (!param->suff.isEmpty()) { + display->setFont(param->suff_fnt.isEmpty() ? parentFont : param->suff_fnt); + display->print(param->suff); + } + } + + void showXXX(Display *display, ParamCollection *param, uint8_t page) { + size_t linesPerPage = display->getLines(); + size_t line_first = _page_n * linesPerPage; + size_t line_last = line_first + linesPerPage - 1; + + display->startRefresh(); + + size_t lineOfPage = 0; + for (size_t n = line_first; n <= line_last; n++) { + auto entry = param->get(n); + if (entry) { + entry->draw(_display, lineOfPage); + lineOfPage++; + } else { + break; + } + } + display->endRefresh(); + } + + void drawPage(Display *display, ParamCollection *params, DisplayPage *page) { + display->setFont(page->font); + display->initCursor(); + + auto keys = page->key; + D_LOG("page keys: %s\r\n", keys.c_str()); + size_t l = 0; + auto line_keys = slice(keys, l, '#'); + while (!line_keys.isEmpty()) { + if (page->valign.equalsIgnoreCase("center")) { + display->getCursor()->moveY((display->getHeight() / 2) - display->getMaxCharHeight() / 2); + } + D_LOG("line keys: %s\r\n", keys.c_str()); + size_t n = 0; + auto key = slice(line_keys, n, ','); + while (!key.isEmpty()) { + D_LOG("key: %s\r\n", key.c_str()); + auto entry = params->find(key.c_str()); + if (entry && entry->updated) { + if (n) display->print(" "); + printParam(display, entry, page->font); + } + key = slice(line_keys, ++n, ','); + } + display->getCursor()->lineFeed(); + line_keys = slice(keys, ++l, '#'); + } + } + + // Режим пользовательской разбивки параметров по страницам + void showManual(Display *display, ParamCollection *param) { + auto page = getPage(_n); + + if (display->isNeedsRefresh() || _pageChanged) { + D_LOG("[Display] page: %d\r\n", _n); + display->setRotation(page->rotate); + display->startRefresh(); + drawPage(display, param, page); + display->endRefresh(); + _pageChanged = false; + } + + if (_context->autoPage && millis() >= (_lastPageChange + page->time)) { + // Если это была последняя начинаем с начала + if (++_n > (getPageCount() - 1)) _n = 0; + _pageChanged = true; + _lastPageChange = millis(); + } + } + + // Режим авто разбивки параметров по страницам + void showAuto(Display *display, ParamCollection *param) { + size_t param_count = param->count(); + + if (!param_count) return; + + display->setFont(_context->font); + display->initCursor(); + + size_t last_n = _n; + if (display->isNeedsRefresh() || _pageChanged) { + //D_LOG("n: %d/%d\r\n", _n, param_count); + last_n = draw(display, param, _n); + } + + if (_context->autoPage && millis() >= (_lastPageChange + _context->pageTime)) { + _n = last_n; + if (_n >= param_count) _n = 0; + _pageChanged = true; + _lastPageChange = millis(); + } + } + + void show() { + if (extParams && _display) { + extParams->load(); + + if (isAutoPage()) { + showAuto(_display, extParams); + } else { + showManual(_display, extParams); + } + } + } + + bool isAutoPage() { + return !getPageCount(); + } + + uint8_t getPageCount() { + return page.size(); + } + + DisplayPage* getPage(uint8_t index) { + return &page.at(index); + } +}; + + +DisplayImplementation* displayImpl = nullptr; + + +class U8g2lib : public IoTItem { + private: + uint8_t _pageNum = 0; + + public: + U8g2lib(String parameters) : IoTItem(parameters) { + DisplayHardwareSettings *context = new DisplayHardwareSettings(); + if (!context) { + D_LOG("[Display] disabled"); + return; + } + + jsonRead(parameters, "update", context->update); + jsonRead(parameters, "font", context->font); + + int rotate; + jsonRead(parameters, "rotation", rotate); + context->rotate = parse_rotation(rotate); + + jsonRead(parameters, "contrast", context->contrast); + jsonRead(parameters, "autoPage", context->autoPage); + jsonRead(parameters, "pageTime", context->pageTime); + + bool itsFirstDisplayInit = false; + if (!displayImpl) { + // Значит это первый элемент U8g2lib в конфигурации - Инициализируем дисплей + itsFirstDisplayInit = true; + int dc = U8X8_PIN_NONE, cs = U8X8_PIN_NONE, data = U8X8_PIN_NONE, clock = U8X8_PIN_NONE, rst = U8X8_PIN_NONE; + jsonRead(parameters, "dc", dc); + jsonRead(parameters, "cs", cs); + jsonRead(parameters, "data", data); + jsonRead(parameters, "clock", clock); + jsonRead(parameters, "rst", rst); + if (dc == -1) dc = U8X8_PIN_NONE; + if (cs == -1) cs = U8X8_PIN_NONE; + if (data == -1) data = U8X8_PIN_NONE; + if (clock == -1) clock = U8X8_PIN_NONE; + if (rst == -1) rst = U8X8_PIN_NONE; + + String type; + jsonRead(parameters, "oledType", type); + U8G2* libObj = nullptr; + if (type.startsWith("ST")) { + libObj = new U8G2_ST7565_ERC12864_F_4W_SW_SPI(U8G2_R0, clock, data, cs, dc, rst); + } + else if (type.startsWith("SS_I2C")) { + // libObj = new U8G2_SSD1306_128X64_VCOMH0_F_SW_I2C(U8G2_R0, clock, data, rst); + libObj = new U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C(U8G2_R0, clock, data, rst); + + } + else if (type.startsWith("SS_SPI")) { + libObj = new U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI(U8G2_R0, clock, data, cs, dc, rst); + } + else if (type.startsWith("SH")) { + libObj = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R0, rst, clock, data); + } + + if (!libObj) { + D_LOG("[Display] disabled"); + return; + } + + Display *_display = new Display(libObj, context); + if (!_display) { + D_LOG("[Display] disabled"); + return; + } + + if (!extParams) extParams = new ParamCollection(); + + displayImpl = new DisplayImplementation(context, _display); + if (!displayImpl) { + D_LOG("[Display] disabled"); + return; + } + } + + // добавляем страницу, если указан ID для отображения + String id2show; + jsonRead(parameters, "id2show", id2show); + if (!id2show.isEmpty()) { + auto item = DisplayPage( + id2show, + context->pageTime, + context->rotate, + context->font, + context->pageFormat, + context->valign + ); + _pageNum = displayImpl->page.size(); + displayImpl->page.push_back(item); + if (!itsFirstDisplayInit) delete context; // если это не первый вызов, то контекст имеет временный характер только для создания страницы + } + } + + void doByInterval() { + if (displayImpl) displayImpl->show(); + } + + IoTValue execute(String command, std::vector& param) { + if (displayImpl) + if (command == "nextPage") { + displayImpl->nextPage(); + } else if (command == "prevPage") { + displayImpl->prevPage(); + } else if (command == "rotPage") { + displayImpl->rotPage(); + } else if (command == "gotoPage") { + if (param.size() == 1) { + displayImpl->gotoPage(param[0].valD); + } else { + displayImpl->gotoPage(_pageNum); + } + } else if (command == "setAutoPage") { + if (param.size() == 1) { + displayImpl->setAutoPage(param[0].valD); + } + } + + return {}; + } + + ~U8g2lib() { + if (displayImpl) { + delete displayImpl; + displayImpl = nullptr; + } + }; +}; + +void* getAPI_U8g2lib(String subtype, String param) { + if (subtype == F("U8g2lib")) { + // SerialPrint("[Display]", "param1: ", param); + return new U8g2lib(param); + } else { + // элемент не наш, но проверяем на налличие модификаторов, которые нужны для модуля + // вынимаем ID элемента и значения pref и suff связанные с ним + if (!extParams) extParams = new ParamCollection(); + extParams->loadExtParamData(param); + return nullptr; + } +} diff --git a/src/modules/display/U8g2lib/example_config.json b/src/modules/display/U8g2lib/example_config.json new file mode 100644 index 00000000..48809c20 --- /dev/null +++ b/src/modules/display/U8g2lib/example_config.json @@ -0,0 +1,217 @@ +{ + "mark": "iotm", + "config": [ + { + "global": 0, + "type": "Reading", + "subtype": "VButton", + "id": "btn", + "needSave": 0, + "widget": "toggle", + "page": "Ввод", + "descr": "ТестКнопка", + "int": "0", + "val": "0", + "value_fnt": "siji", + "gliphs": "" + }, + { + "global": 0, + "type": "Writing", + "subtype": "Timer", + "id": "timer", + "widget": "anydataDef", + "page": "Ввод", + "descr": "Таймер", + "int": 1, + "countDown": "99", + "ticker": 1, + "repeat": 1, + "needSave": 0, + "pref": "ТАЙМЕР: ", + "suff": " сек", + "round": "0" + }, + { + "global": 0, + "type": "Reading", + "subtype": "Variable", + "id": "time", + "needSave": 0, + "widget": "anydataRed", + "page": "Ввод", + "descr": "Время", + "int": "0", + "val": "", + "pref": " ⏰️", + "pref_fnt": "unifont" + }, + { + "global": 0, + "type": "Reading", + "subtype": "Variable", + "id": "var", + "needSave": 0, + "widget": "inputTxt", + "page": "Ввод", + "descr": "Текст", + "int": "0", + "val": "☀️-☁️-☂️-☃️-☄️", + "map": "1024,1024,1,100", + "plus": 0, + "multiply": 1, + "round": 0, + "pref": "текст: ", + "value_fnt": "unifont" + }, + { + "global": 0, + "type": "Reading", + "subtype": "Variable", + "id": "ip", + "needSave": 0, + "widget": "anydataDef", + "page": "Ввод", + "descr": "IP", + "int": "0", + "val": "", + "pref": "IP: " + }, + { + "type": "Reading", + "subtype": "U8g2lib", + "id": "page1", + "widget": "nil", + "page": "", + "descr": "", + "oledType": "SS_I2C", + "int": "1", + "font": "c6x13", + "contrast": "200", + "rotation": "0", + "autoPage": "0", + "pageTime": "10000", + "dc": 19, + "cs": "-1", + "data": "21", + "clock": "22", + "rst": -1, + "id2show": "timer,lvl#ip" + }, + { + "type": "Reading", + "subtype": "U8g2lib", + "id": "page2", + "widget": "nil", + "page": "", + "descr": "", + "oledType": "SS_I2C", + "int": 1, + "update": 500, + "font": "c6x13", + "contrast": "150", + "rotation": "0", + "autoPage": "0", + "pageTime": 3000, + "id2show": "var#btn,time", + "dc": "-1", + "cs": "-1", + "data": "-1", + "clock": "-1", + "rst": -1 + }, + { + "global": 0, + "type": "Reading", + "subtype": "VButton", + "id": "autoPage", + "needSave": 0, + "widget": "toggle", + "page": "Ввод", + "descr": "autoPage", + "int": "0", + "val": "0" + }, + { + "global": 0, + "type": "Reading", + "subtype": "VButton", + "id": "nextPage", + "needSave": 0, + "widget": "toggle", + "page": "Ввод", + "descr": "nextPage", + "int": "0", + "val": "0" + }, + { + "global": 0, + "type": "Reading", + "subtype": "VButton", + "id": "prevPage", + "needSave": 0, + "widget": "toggle", + "page": "Ввод", + "descr": "prevPage", + "int": "0", + "val": "0" + }, + { + "global": 0, + "type": "Reading", + "subtype": "Variable", + "id": "pageN", + "needSave": 0, + "widget": "inputDgt", + "page": "Ввод", + "descr": "pageN", + "int": "0", + "val": "0.0", + "map": "1024,1024,1,100", + "plus": 0, + "multiply": 1, + "round": 0 + }, + { + "global": 0, + "type": "Reading", + "subtype": "VButton", + "id": "rotPage", + "needSave": 0, + "widget": "toggle", + "page": "Ввод", + "descr": "rotPage", + "int": "0", + "val": "0" + }, + { + "global": 0, + "type": "Reading", + "subtype": "AnalogAdc", + "id": "lvl", + "widget": "anydataRed", + "page": "Ввод", + "descr": "Уровень", + "map": "1,1024,1,5", + "plus": 0, + "multiply": 1, + "round": "0", + "pin": "34", + "int": "1", + "avgSteps": 1, + "pref": " ", + "value_fnt": "siji", + "gliphs": "" + } + ] +} + +scenario=>if timer then { +ip = getIP() +time = gethhmmss() +} +if autoPage then page1.setAutoPage(1) else page1.setAutoPage(0) +if nextPage < 2 then page1.nextPage() +if prevPage < 2 then page1.prevPage() +if rotPage < 2 then page1.rotPage() +if pageN != "" then page1.gotoPage(pageN) \ No newline at end of file