mirror of
https://github.com/IoTManagerProject/IoTManager.git
synced 2026-03-26 14:12:16 +03:00
Добавляем модуль U8g2lib для работы с дисплеями
This commit is contained in:
BIN
src/modules/display/U8g2lib.zip
Normal file
BIN
src/modules/display/U8g2lib.zip
Normal file
Binary file not shown.
631
src/modules/display/U8g2lib/DisplayTypes.h
Normal file
631
src/modules/display/U8g2lib/DisplayTypes.h
Normal file
@@ -0,0 +1,631 @@
|
||||
#pragma once
|
||||
#include "Global.h"
|
||||
#include <U8g2lib.h>
|
||||
#include <Print.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// #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<uint16_t>();
|
||||
// // rotate = parse_rotation(obj["rotate"].as<int>());
|
||||
// // font = obj["font"].as<char*>();
|
||||
// // valign = obj["valign"].as<char*>();
|
||||
// // format = obj["format"].as<char*>();
|
||||
// }
|
||||
|
||||
// auto item = DisplayPage( pageObj["key"].as<char*>(), _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<Param> _item;
|
||||
|
||||
public:
|
||||
void load() {
|
||||
for (std::list<IoTItem*>::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;
|
||||
}
|
||||
};
|
||||
419
src/modules/display/U8g2lib/U8g2lib.cpp
Normal file
419
src/modules/display/U8g2lib/U8g2lib.cpp
Normal file
@@ -0,0 +1,419 @@
|
||||
#include "Global.h"
|
||||
#include "classes/IoTItem.h"
|
||||
#include <U8g2lib.h>
|
||||
#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<Param *> Line;
|
||||
// текущая
|
||||
size_t _page_n{0};
|
||||
// struct Page {
|
||||
// std::vector<Line *> 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<DisplayPage> 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<IoTValue>& 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;
|
||||
}
|
||||
}
|
||||
217
src/modules/display/U8g2lib/example_config.json
Normal file
217
src/modules/display/U8g2lib/example_config.json
Normal file
@@ -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)
|
||||
Reference in New Issue
Block a user