mirror of
https://github.com/IoTManagerProject/IoTManager.git
synced 2026-03-26 14:12:16 +03:00
@@ -13,6 +13,8 @@
|
||||
# python PrepareProject.py --profile <ИмяФайла>
|
||||
# python PrepareProject.py -p <ИмяФайла>
|
||||
#
|
||||
# Используя параметры -b или --board <board_name> можно уточнить для какой платы нужно подготовить проект
|
||||
#
|
||||
# поддерживаемые контроллеры (профили):
|
||||
# esp8266_4mb
|
||||
# esp8266_16mb
|
||||
@@ -223,12 +225,12 @@ allAPI_head = ""
|
||||
allAPI_exec = ""
|
||||
for activModuleName in activeModulesName:
|
||||
allAPI_head = allAPI_head + "\nvoid* getAPI_" + activModuleName + "(String subtype, String params);"
|
||||
allAPI_exec = allAPI_exec + "\nif ((tmpAPI = getAPI_" + activModuleName + "(subtype, params)) != nullptr) return tmpAPI;"
|
||||
allAPI_exec = allAPI_exec + "\nif ((tmpAPI = getAPI_" + activModuleName + "(subtype, params)) != nullptr) foundAPI = tmpAPI;"
|
||||
apicpp = '#include "ESPConfiguration.h"\n'
|
||||
apicpp = apicpp + allAPI_head
|
||||
apicpp = apicpp + '\n\nvoid* getAPI(String subtype, String params) {\nvoid* tmpAPI;'
|
||||
apicpp = apicpp + '\n\nvoid* getAPI(String subtype, String params) {\nvoid* tmpAPI; void* foundAPI = nullptr;'
|
||||
apicpp = apicpp + allAPI_exec
|
||||
apicpp = apicpp + '\nreturn nullptr;\n}'
|
||||
apicpp = apicpp + '\nreturn foundAPI;\n}'
|
||||
with open('src/modules/API.cpp', 'w') as f:
|
||||
f.write(apicpp)
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@ config.read("platformio.ini")
|
||||
deviceName = config["platformio"]["default_envs"]
|
||||
|
||||
homeDir = os.path.expanduser('~')
|
||||
os.system(homeDir + "\.platformio\penv\Scripts\pio run")
|
||||
os.system(homeDir + "\.platformio\penv\Scripts\pio run -t buildfs --disable-auto-clean")
|
||||
os.system(homeDir + "/.platformio/penv/Scripts/pio run")
|
||||
os.system(homeDir + "/.platformio/penv/Scripts/pio run -t buildfs --disable-auto-clean")
|
||||
|
||||
if copyFileIfExist("firmware.bin", deviceName) and copyFileIfExist("littlefs.bin", deviceName):
|
||||
copyFileIfExist("partitions.bin", deviceName)
|
||||
|
||||
@@ -12,6 +12,8 @@ uint8_t hexStringToUint8(const String& hex);
|
||||
|
||||
uint16_t hexStringToUint16(const String& hex);
|
||||
|
||||
uint32_t hexStringToUint32(const String& hex);
|
||||
|
||||
String selectToMarkerLast(String str, const String& found);
|
||||
|
||||
String selectToMarker(String str, const String& found);
|
||||
@@ -49,3 +51,5 @@ unsigned char ChartoHex(char ch);
|
||||
std::vector<String> splitStr(const String& str, const String& delimiter);
|
||||
|
||||
bool strInVector(const String& str, const std::vector<String>& vec);
|
||||
|
||||
String getUtf8CharByIndex(const String& utf8str, int index);
|
||||
|
||||
111
run.py
Normal file
111
run.py
Normal file
@@ -0,0 +1,111 @@
|
||||
# Скрипт для простой прошивки ESP с учетом профиля из myProfile.json и поиска нужной кнопочки в интерфейсе PlatformIO
|
||||
# Если ничего не указано в параметрах, то выполняется последовательно набор команд:
|
||||
# 1. run PrepareProject.py
|
||||
# 2. platformio run -t clean
|
||||
# 3. platformio run -t uploadfs -е default_envs
|
||||
# 4. platformio run -t upload -е default_envs
|
||||
# 5. platformio run -t monitor
|
||||
# где default_envs - это параметр default_envs из myProfile.json
|
||||
#
|
||||
# Если указан параметр -p или --profile <ИмяФайла>, то выполняется первая команда PrepareProject.py -p <ИмяФайла>
|
||||
# Если указан параметр -b или --board <board_name>, то выполняется первая команда PrepareProject.py -b <board_name>
|
||||
# Если указан парамтер -l или --lite, то пропускаются команды 1, 2 и 5 с предварительной компиляцией
|
||||
# Если указан параметр -d или --debug, то выполняется только команда 4 с предварительной компиляцией
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import json
|
||||
|
||||
def get_platformio_path():
|
||||
"""
|
||||
Возвращает путь к PlatformIO в зависимости от операционной системы.
|
||||
"""
|
||||
if os.name == 'nt': # Windows
|
||||
return os.path.join(os.environ['USERPROFILE'], '.platformio', 'penv', 'Scripts', 'pio.exe')
|
||||
else: # Linux/MacOS
|
||||
return os.path.join(os.environ['HOME'], '.platformio', 'penv', 'bin', 'pio')
|
||||
|
||||
def load_default_envs(profile_path="myProfile.json"):
|
||||
"""
|
||||
Загружает значение default_envs из файла myProfile.json.
|
||||
"""
|
||||
if not os.path.isfile(profile_path):
|
||||
print(f"Файл профиля {profile_path} не найден.")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
with open(profile_path, 'r', encoding='utf-8') as file:
|
||||
profile_data = json.load(file)
|
||||
return profile_data["projectProp"]["platformio"]["default_envs"]
|
||||
except KeyError:
|
||||
print("Не удалось найти ключ 'default_envs' в myProfile.json.")
|
||||
sys.exit(1)
|
||||
except json.JSONDecodeError:
|
||||
print(f"Ошибка при чтении файла {profile_path}: некорректный JSON.")
|
||||
sys.exit(1)
|
||||
|
||||
def run_command(command):
|
||||
"""
|
||||
Выполняет указанную команду в subprocess.
|
||||
"""
|
||||
try:
|
||||
print(f"Выполнение команды: {' '.join(command)}")
|
||||
subprocess.run(command, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Ошибка при выполнении команды: {e}")
|
||||
sys.exit(e.returncode)
|
||||
|
||||
def run_platformio():
|
||||
"""
|
||||
Основная логика выполнения команд в зависимости от параметров.
|
||||
"""
|
||||
pio_path = get_platformio_path()
|
||||
|
||||
# Проверяем, существует ли PlatformIO
|
||||
if not os.path.isfile(pio_path):
|
||||
print(f"PlatformIO не найден по пути: {pio_path}")
|
||||
sys.exit(1)
|
||||
# print(f"PlatformIO найден по пути: {pio_path}")
|
||||
|
||||
# Читаем аргументы командной строки
|
||||
args = sys.argv[1:]
|
||||
lite_mode = '-l' in args or '--lite' in args
|
||||
debug_mode = '-d' in args or '--debug' in args
|
||||
profile_index = next((i for i, arg in enumerate(args) if arg in ('-p', '--profile')), None)
|
||||
profile_file = args[profile_index + 1] if profile_index is not None and len(args) > profile_index + 1 else "myProfile.json"
|
||||
|
||||
# Загружаем default_envs из myProfile.json, если не указан параметр -b, который имеет больший приоритет
|
||||
board_index = next((i for i, arg in enumerate(args) if arg in ('-b', '--board')), None)
|
||||
default_envs = args[board_index + 1] if board_index is not None and len(args) > board_index + 1 else load_default_envs(profile_path=profile_file)
|
||||
|
||||
print(f"Используем default_envs: {default_envs}")
|
||||
print(f"Режим Lite: {lite_mode}, Режим отладки: {debug_mode}")
|
||||
print(f"Профиль: {profile_file}")
|
||||
|
||||
# Выполнение команд в зависимости от параметров
|
||||
if not lite_mode and not debug_mode:
|
||||
# Полный набор команд
|
||||
run_command(['python', 'PrepareProject.py', '-p', profile_file])
|
||||
|
||||
# Добавляем сообщение о необходимости дождаться завершения обновления конфигурации
|
||||
input(f"\x1b[1;31;42m Подождите завершения обновления конфигурации PlatformIO, затем нажмите Ввод для продолжения...\x1b[0m")
|
||||
|
||||
run_command([pio_path, 'run', '-t', 'clean'])
|
||||
run_command([pio_path, 'run', '-t', 'uploadfs', '--environment', default_envs])
|
||||
run_command([pio_path, 'run', '-t', 'upload', '--environment', default_envs])
|
||||
run_command([pio_path, 'run', '-t', 'monitor'])
|
||||
elif lite_mode:
|
||||
# Упрощенный режим (пропускаем команды 1, 2 и 5)
|
||||
run_command([pio_path, 'run', '-t', 'buildfs', '--environment', default_envs])
|
||||
run_command([pio_path, 'run', '-t', 'uploadfs', '--environment', default_envs])
|
||||
run_command([pio_path, 'run', '--environment', default_envs])
|
||||
run_command([pio_path, 'run', '-t', 'upload', '--environment', default_envs])
|
||||
elif debug_mode:
|
||||
# Режим отладки (только команда 4)
|
||||
run_command([pio_path, 'run', '--environment', default_envs])
|
||||
run_command([pio_path, 'run', '-t', 'upload', '--environment', default_envs])
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_platformio()
|
||||
|
||||
@@ -292,6 +292,11 @@ class CallExprAST : public ExprAST {
|
||||
ret.valD = Item->getIntFromNet();
|
||||
ret.isDecimal = true;
|
||||
return &ret;
|
||||
|
||||
} else if (Cmd == F("doByInterval")) { // вызываем системную функцию периодического выполнения вне таймера
|
||||
Item->doByInterval();
|
||||
ret = Item->value;
|
||||
return &ret;
|
||||
}
|
||||
|
||||
// если все же все ок, то готовим параметры для передачи в модуль
|
||||
@@ -305,6 +310,15 @@ class CallExprAST : public ExprAST {
|
||||
return nullptr; // ArgsAsIoTValue.push_back(zeroIotVal);
|
||||
}
|
||||
|
||||
if (Cmd == F("setInterval")) { // меняем интервал выполнения задач модуля налету
|
||||
if (ArgsAsIoTValue.size() == 1) {
|
||||
Item->setInterval(ArgsAsIoTValue[0].valD);
|
||||
ret.valD = Item->getInterval();
|
||||
ret.isDecimal = true;
|
||||
return &ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = Item->execute(Cmd, ArgsAsIoTValue); // вызываем команду из модуля напрямую с передачей всех аргументов
|
||||
|
||||
// if (ret.isDecimal) Serial.printf("Call from CallExprAST ID = %s, Command = %s, exec result = %f\n", Callee.c_str(), Cmd.c_str(), ret.valD);
|
||||
@@ -345,6 +359,7 @@ enum SysOp {
|
||||
sysop_getUptime,
|
||||
sysop_mqttIsConnect,
|
||||
sysop_wifiIsConnect,
|
||||
sysop_setInterval,
|
||||
sysop_addPortMap
|
||||
};
|
||||
|
||||
@@ -452,6 +467,11 @@ IoTValue sysExecute(SysOp command, std::vector<IoTValue> ¶m) {
|
||||
case sysop_wifiIsConnect:
|
||||
value.valD = isNetworkActive();
|
||||
break;
|
||||
case sysop_setInterval:
|
||||
if (param.size() == 1) {
|
||||
|
||||
}
|
||||
break;
|
||||
case sysop_addPortMap:
|
||||
if (param.size() == 5) {
|
||||
addPortMap(param[0].valS, param[1].valS, param[2].valD, param[3].valS, param[4].valD);
|
||||
@@ -514,7 +534,9 @@ class SysCallExprAST : public ExprAST {
|
||||
else if (Callee == F("mqttIsConnect"))
|
||||
operation = sysop_mqttIsConnect;
|
||||
else if (Callee == F("wifiIsConnect"))
|
||||
operation = sysop_wifiIsConnect;
|
||||
operation = sysop_wifiIsConnect;
|
||||
else if (Callee == F("setInterval"))
|
||||
operation = sysop_setInterval;
|
||||
else if (Callee == F("addPortMap"))
|
||||
operation = sysop_addPortMap;
|
||||
else
|
||||
|
||||
@@ -140,6 +140,8 @@ class DwinI : public IoTUart {
|
||||
void onModuleOrder(String &key, String &value) {
|
||||
if (key == "uploadUI") {
|
||||
//SerialPrint("i", F("DwinI"), "Устанавливаем UI: " + value);
|
||||
if (value != "") uartPrintHex(value.c_str());
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
332
src/modules/display/LedFX/LedFX.cpp
Normal file
332
src/modules/display/LedFX/LedFX.cpp
Normal file
@@ -0,0 +1,332 @@
|
||||
#include "Global.h"
|
||||
#include "classes/IoTItem.h"
|
||||
#include "ESPConfiguration.h"
|
||||
#include <WS2812FX.h>
|
||||
|
||||
|
||||
|
||||
WS2812FX *_glob_strip = nullptr; // глобальный указатель на WS2812FX для использования в функциях для кастомных эффектов
|
||||
std::vector<IoTValue*> vuMeterBands; // массив указателей на элементы IoTValue, которые будут использоваться для передачи данных в эффект VU Meter
|
||||
|
||||
uint16_t vuMeter2(void) {
|
||||
// функция взята из WS2812FX.cpp для демонстрации возможности создания своего алгоритма эффекта
|
||||
// в данном случае - VU Meter имеет тот же смысл отображения уровня сигнала, что и в WS2812FX с сохранением алгоритма вывода данных из нескольких источников
|
||||
// но вместо использования внешнего источника данных для встроенного эффекта, мы используем данные из других элементов конфигурации IoTM
|
||||
// Если данные не поступают (IoTValue.valS == "1"), то используется генерация случайных чисел для демонстрации работы эффекта
|
||||
|
||||
if (_glob_strip == nullptr) return 0; // Проверяем, инициализирована ли библиотека WS2812FX
|
||||
|
||||
uint16_t numBands = vuMeterBands.size(); // Получаем количество полос VU Meter из массива указателей
|
||||
if (numBands == 0) return 0; // Если нет полос, выходим из функции
|
||||
|
||||
WS2812FX::Segment* seg = _glob_strip->getSegment();
|
||||
uint16_t seglen = seg->stop - seg->start + 1;
|
||||
uint16_t bandSize = seglen / numBands;
|
||||
|
||||
if (vuMeterBands[0]->valS == "R")
|
||||
for (uint8_t i=0; i < numBands; i++) {
|
||||
int randomData = vuMeterBands[i]->valD + _glob_strip->random8(32) - _glob_strip->random8(32);
|
||||
vuMeterBands[i]->valD = (randomData < 0 || randomData > 255) ? 128 : randomData;
|
||||
}
|
||||
|
||||
for(uint8_t i=0; i<numBands; i++) {
|
||||
// SerialPrint("i", "LedFX", String(vuMeterBands[i]->valD));
|
||||
uint8_t scaledBand = (vuMeterBands[i]->valD * bandSize) / 256;
|
||||
for(uint16_t j=0; j<bandSize; j++) {
|
||||
uint16_t index = seg->start + (i * bandSize) + j;
|
||||
if(j <= scaledBand) {
|
||||
if(j < bandSize - 4) _glob_strip->setPixelColor(index, GREEN);
|
||||
else if(j < bandSize - 2) _glob_strip->setPixelColor(index, YELLOW);
|
||||
else _glob_strip->setPixelColor(index, RED);
|
||||
} else {
|
||||
_glob_strip->setPixelColor(index, BLACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
_glob_strip->setCycle();
|
||||
|
||||
return seg->speed;
|
||||
}
|
||||
|
||||
|
||||
class LedFX : public IoTItem
|
||||
{
|
||||
private:
|
||||
WS2812FX *_strip;
|
||||
|
||||
int _data_pin = 2;
|
||||
int _numLeds = 1;
|
||||
int _brightness = 100;
|
||||
int _speed = 15000;
|
||||
int _effectsMode = 0;
|
||||
int _valueMode = 0;
|
||||
int _color = 0xFF0000; // default color red
|
||||
|
||||
uint8_t _BrightnessFadeOutStep = 0;
|
||||
uint8_t _BrightnessFadeOutMin = 1;
|
||||
uint8_t _BrightnessFadeInStep = 0;
|
||||
uint8_t _BrightnessFadeInMax = 50;
|
||||
|
||||
public:
|
||||
LedFX(String parameters) : IoTItem(parameters) {
|
||||
jsonRead(parameters, F("data_pin"), _data_pin);
|
||||
jsonRead(parameters, F("speed"), _speed);
|
||||
jsonRead(parameters, F("numLeds"), _numLeds);
|
||||
jsonRead(parameters, F("brightness"), _brightness);
|
||||
|
||||
String tmpStr;
|
||||
jsonRead(parameters, F("color"), tmpStr);
|
||||
_color = hexStringToUint32(tmpStr);
|
||||
|
||||
jsonRead(parameters, F("effectsMode"), _effectsMode);
|
||||
jsonRead(parameters, F("valueMode"), _valueMode);
|
||||
|
||||
|
||||
//_strip = new WS2812FX(_numLeds, _data_pin, NEO_BRG + NEO_KHZ400); // SM16703
|
||||
_strip = new WS2812FX(_numLeds, _data_pin, NEO_GRB + NEO_KHZ800); // WS2812B
|
||||
|
||||
if (_strip != nullptr) {
|
||||
_glob_strip = _strip; // Сохраняем указатель в глобальной переменной
|
||||
|
||||
_strip->init();
|
||||
_strip->setBrightness(_brightness);
|
||||
_strip->setSpeed(_speed);
|
||||
_strip->setMode(_effectsMode);
|
||||
_strip->setColor(_color);
|
||||
if (_effectsMode >= 0) _strip->start();
|
||||
}
|
||||
}
|
||||
|
||||
void fadeOutNonBlocking() {
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (!_strip) return;
|
||||
|
||||
static unsigned long lastUpdate = 0; // Время последнего обновления
|
||||
unsigned long now = millis();
|
||||
if (now - lastUpdate >= 70) { // Проверяем, прошло ли достаточно времени
|
||||
lastUpdate = now;
|
||||
|
||||
if (_BrightnessFadeOutStep > 0) {
|
||||
int currentBrightness = _strip->getBrightness(); // Получаем текущую яркость
|
||||
currentBrightness -= _BrightnessFadeOutStep;
|
||||
if (currentBrightness < _BrightnessFadeOutMin) {
|
||||
currentBrightness = _BrightnessFadeOutMin; // Убедимся, что яркость не уйдет в отрицательные значения
|
||||
_BrightnessFadeOutStep = 0; // Останавливаем затухание
|
||||
}
|
||||
_strip->setBrightness(currentBrightness);
|
||||
_strip->show();
|
||||
}
|
||||
|
||||
if (_BrightnessFadeInStep > 0) {
|
||||
int currentBrightness = _strip->getBrightness(); // Получаем текущую яркость
|
||||
currentBrightness += _BrightnessFadeInStep;
|
||||
if (currentBrightness > _BrightnessFadeInMax) {
|
||||
currentBrightness = _BrightnessFadeInMax; // Убедимся, что яркость не уйдет за пределы
|
||||
_BrightnessFadeInStep = 0; // Останавливаем затухание
|
||||
}
|
||||
_strip->setBrightness(currentBrightness);
|
||||
_strip->show();
|
||||
}
|
||||
}
|
||||
|
||||
_strip->service();
|
||||
IoTItem::loop();
|
||||
}
|
||||
|
||||
void doByInterval() {
|
||||
|
||||
}
|
||||
|
||||
IoTValue execute(String command, std::vector<IoTValue> ¶m) {
|
||||
if (!_strip) return {};
|
||||
if (command == "fadeOut") {
|
||||
if (param.size() == 2) {
|
||||
_BrightnessFadeOutMin = param[0].valD;
|
||||
_BrightnessFadeOutStep = param[1].valD;
|
||||
SerialPrint("E", "Strip LedFX", "BrightnessFadeOut");
|
||||
}
|
||||
|
||||
} else if (command == "fadeIn") {
|
||||
if (param.size() == 2) {
|
||||
_BrightnessFadeInMax = param[0].valD;
|
||||
_BrightnessFadeInStep = param[1].valD;
|
||||
SerialPrint("E", "Strip LedFX", "BrightnessFadeIn");
|
||||
}
|
||||
|
||||
} else if (command == "setColor") {
|
||||
if (param.size() == 1) {
|
||||
_color = hexStringToUint32(param[0].valS);
|
||||
_strip->setColor(_color);
|
||||
_strip->show();
|
||||
SerialPrint("E", "Strip LedFX", "setColor:" + param[0].valS);
|
||||
}
|
||||
|
||||
} else if (command == "setEffect") {
|
||||
if (param.size() == 1) {
|
||||
if (param[0].valD < 0 || param[0].valD > 79)
|
||||
_effectsMode = random(0, 79);
|
||||
else
|
||||
_effectsMode = param[0].valD;
|
||||
_strip->setMode(_effectsMode);
|
||||
_strip->show();
|
||||
_strip->start();
|
||||
SerialPrint("E", "Strip LedFX", "setEffect:" + param[0].valS);
|
||||
}
|
||||
|
||||
} else if (command == "setSpeed") {
|
||||
if (param.size() == 1) {
|
||||
_speed = param[0].valD;
|
||||
_strip->setSpeed(_speed);
|
||||
_strip->show();
|
||||
SerialPrint("E", "Strip LedFX", "setSpeed:" + param[0].valS);
|
||||
}
|
||||
|
||||
} else if (command == "setBrightness") {
|
||||
if (param.size() == 1) {
|
||||
_brightness = param[0].valD;
|
||||
_strip->setBrightness(_brightness);
|
||||
_strip->show();
|
||||
SerialPrint("E", "Strip LedFX", "setBrightness:" + param[0].valS);
|
||||
}
|
||||
|
||||
} else if (command == "stop") {
|
||||
_strip->stop();
|
||||
SerialPrint("E", "Strip LedFX", "stop");
|
||||
|
||||
} else if (command == "start") {
|
||||
_strip->start();
|
||||
SerialPrint("E", "Strip LedFX", "start");
|
||||
|
||||
} else if (command == "pause") {
|
||||
_strip->pause();
|
||||
SerialPrint("E", "Strip LedFX", "pause");
|
||||
|
||||
} else if (command == "resume") {
|
||||
_strip->resume();
|
||||
SerialPrint("E", "Strip LedFX", "resume");
|
||||
|
||||
} else if (command == "setSegment") {
|
||||
if (param.size() == 6) {
|
||||
_strip->setSegment(param[0].valD, param[1].valD, param[2].valD, param[3].valD, hexStringToUint32(param[4].valS), param[5].valD);
|
||||
_strip->show();
|
||||
_strip->start();
|
||||
SerialPrint("E", "Strip LedFX", "setSegment:" + param[0].valS + " start:" + param[1].valS + " stop:" + param[2].valS + " mode:" + param[3].valS, " color:" + param[4].valS + " speed:" + param[5].valS);
|
||||
}
|
||||
|
||||
} else if(command == "noShowOne"){
|
||||
if (param.size() == 1) {
|
||||
_strip->setPixelColor(param[0].valD, _strip->Color(0, 0, 0));
|
||||
_strip->show();
|
||||
SerialPrint("E", "Strip LedFX", "noShowOne");
|
||||
}
|
||||
|
||||
} else if (command == "showLed"){
|
||||
if (param.size() == 2) {
|
||||
uint32_t color = hexStringToUint32(param[1].valS);
|
||||
_strip->setPixelColor(param[0].valD, color);
|
||||
_strip->show();
|
||||
_strip->start();
|
||||
SerialPrint("E", "Strip LedFX", "showLed:" + param[0].valS + " color:" + param[1].valS);
|
||||
}
|
||||
|
||||
} else if (command == "vuMeter") {
|
||||
if (param.size() == 2) {
|
||||
int bandCnt = param[0].valD;
|
||||
if (param[1].valS == "") {
|
||||
for (int i=0; i < vuMeterBands.size(); i++) {
|
||||
delete vuMeterBands[i];
|
||||
}
|
||||
vuMeterBands.clear();
|
||||
|
||||
for (uint8_t i=0; i < bandCnt; i++) {
|
||||
IoTValue *band = new IoTValue(); // создаем новый элемент IoTValue для полос VU Meter
|
||||
band->valD = 0;
|
||||
band->valS = "R";
|
||||
vuMeterBands.push_back(band); // добавляем указатель в массив
|
||||
}
|
||||
} else {
|
||||
// Очищаем массив vuMeterBands перед заполнением
|
||||
vuMeterBands.clear();
|
||||
|
||||
String id;
|
||||
String idsStr = param[1].valS;
|
||||
// Разделяем строку idsStr на идентификаторы, используя запятую как разделитель
|
||||
while (idsStr.length() > 0) {
|
||||
// Извлекаем идентификатор до первой запятой
|
||||
id = selectToMarker(idsStr, ",");
|
||||
|
||||
// Ищем элемент IoTItem по идентификатору
|
||||
IoTItem* item = findIoTItem(id);
|
||||
if (item != nullptr) {
|
||||
// Добавляем указатель на поле value найденного элемента в vuMeterBands
|
||||
vuMeterBands.push_back(&(item->value));
|
||||
SerialPrint("E", "LedFX", "Добавлен элемент в vuMeterBands: " + id);
|
||||
} else {
|
||||
SerialPrint("E", "LedFX", "Элемент не найден: " + id);
|
||||
}
|
||||
|
||||
int8_t oldSize = idsStr.length();
|
||||
// Удаляем обработанный идентификатор из строки
|
||||
idsStr = deleteBeforeDelimiter(idsStr, ",");
|
||||
if (idsStr.length() == oldSize) {
|
||||
// Если длина строки не изменилась, значит, больше нет запятых и это был последний идентификатор
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
_strip->setCustomMode(vuMeter2);
|
||||
_strip->setMode(FX_MODE_CUSTOM);
|
||||
_strip->start();
|
||||
SerialPrint("E", "Strip LedFX", "vuMeter bands:" + param[0].valS + " IDs to show:" + param[1].valS);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void setValue(const IoTValue& Value, bool genEvent = true) {
|
||||
if (!_strip) return;
|
||||
|
||||
if (_valueMode == 0) {
|
||||
_strip->setMode(Value.valD);
|
||||
_effectsMode = Value.valD;
|
||||
} else if (_valueMode == 1) {
|
||||
_strip->setBrightness(Value.valD);
|
||||
_brightness = Value.valD;
|
||||
} else if (_valueMode == 2) {
|
||||
_color = hexStringToUint32(Value.valS);
|
||||
_strip->setColor(_color);
|
||||
} else if (_valueMode == 3) {
|
||||
_strip->setSpeed(Value.valD);
|
||||
_speed = Value.valD;
|
||||
}
|
||||
|
||||
value = Value;
|
||||
regEvent(value.valD, "LedFX", false, genEvent);
|
||||
}
|
||||
|
||||
~LedFX() {
|
||||
if (_strip != nullptr) {
|
||||
delete _strip;
|
||||
_strip = nullptr;
|
||||
_glob_strip = nullptr; // Обнуляем глобальный указатель
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
void *getAPI_LedFX(String subtype, String param)
|
||||
{
|
||||
if (subtype == F("LedFX")) {
|
||||
return new LedFX(param);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
163
src/modules/display/LedFX/modinfo.json
Normal file
163
src/modules/display/LedFX/modinfo.json
Normal file
@@ -0,0 +1,163 @@
|
||||
{
|
||||
"menuSection": "screens",
|
||||
"configItem": [
|
||||
{
|
||||
"global": 0,
|
||||
"name": "LedFX",
|
||||
"type": "Reading",
|
||||
"subtype": "LedFX",
|
||||
"id": "fl",
|
||||
"widget": "inputTxt",
|
||||
"page": "Кнопки",
|
||||
"descr": "Лента",
|
||||
"int": 15,
|
||||
"needSave": 0,
|
||||
|
||||
"data_pin": "2",
|
||||
"numLeds": "3",
|
||||
"brightness": "50",
|
||||
"speed": "3000",
|
||||
"color": "0xFF0000",
|
||||
"effectsMode": 0,
|
||||
"valueMode": 0
|
||||
}
|
||||
],
|
||||
"about": {
|
||||
"authorName": "Ilya Belyakov",
|
||||
"authorContact": "https://t.me/Biveraxe",
|
||||
"authorGit": "https://github.com/biveraxe",
|
||||
"exampleURL": "https://iotmanager.org/wiki",
|
||||
"specialThanks": "Yuriy Kuneev (https://t.me/Kuneev07)",
|
||||
"moduleName": "LedFX",
|
||||
"moduleVersion": "1.0.1",
|
||||
"moduleDesc": "Позволяет управлять адресными светодиодными лентами WS2812B и аналогичными.",
|
||||
"propInfo": {
|
||||
"int": "Период времени в секундах обновления.",
|
||||
"data_pin": "Пин к которому подключена лента.",
|
||||
"speed": "Скорость обновления ленты.",
|
||||
"numLeds": "Количество пикселей в ленте.",
|
||||
"needSave": "Запись значения элемента в энергонезависимую память",
|
||||
"brightness": "Яркость ленты можно менять из сценария.",
|
||||
"color": "Цвет ленты в формате 0xRRGGBB, например, 0xFF0000 - красный, 0x00FF00 - зеленый, 0x0000FF - синий.",
|
||||
"effectsMode": "Режим эффектов ленты. 0-79. 0 - Статичный цвет.",
|
||||
"valueMode": "Режим применения значения элемета. 0 - установка режима эффектов, 1 - регулирование яркости, 2 - изменение цвета, 3 - изменение скорости."
|
||||
},
|
||||
"title": "Адресная светодиодная лента",
|
||||
"funcInfo": [
|
||||
{
|
||||
"name": "noShowOne",
|
||||
"descr": "Выключить один светодиод на ленте",
|
||||
"params": [
|
||||
"номер пикселя"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "showLed",
|
||||
"descr": "Зажечь один диод",
|
||||
"params": [
|
||||
"номер пикселя",
|
||||
"цвет в формате 0xRRGGBB"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "setBrightness",
|
||||
"descr": "Устанавливает общую яркость ленты от 0 до 255",
|
||||
"params": [
|
||||
"яркость от 0 до 255"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "vuMeter",
|
||||
"descr": "Включает режим VU Meter. Важно что бы элемент ленты был ниже в списке чем элемент с датчиком, ИД которого нужно будет мониторить.",
|
||||
"params": [
|
||||
"Количество каналов для отображения на ленте",
|
||||
"Список ID датчиков через запятую (если указать пустую строку, то каналы будут заполняться случайными числами от 0 до 255)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "setColor",
|
||||
"descr": "Устанавливает цвет ленты в формате 0xRRGGBB, например, 0xFF0000 - красный, 0x00FF00 - зеленый, 0x0000FF - синий.",
|
||||
"params": [
|
||||
"цвет в формате 0xRRGGBB"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "setEffect",
|
||||
"descr": "Устанавливает эффект ленты. 0-79. 0 - Статичный цвет.",
|
||||
"params": [
|
||||
"номер эффекта от 0 до 79"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "setSpeed",
|
||||
"descr": "Устанавливает скорость эффекта от 0 до 255",
|
||||
"params": [
|
||||
"скорость от 0 до 255"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "fadeOut",
|
||||
"descr": "Плавное затухание яркости",
|
||||
"params": [
|
||||
"Целевое значение яркости, до которого будет затухать",
|
||||
"Шаг затухания"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "fadeIn",
|
||||
"descr": "Плавное нарастание яркости",
|
||||
"params": [
|
||||
"Целевое значение яркости, до которого будет нарастать",
|
||||
"Шаг нарастания"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stop",
|
||||
"descr": "Останавливает эффект",
|
||||
"params": []
|
||||
|
||||
},
|
||||
{
|
||||
"name": "start",
|
||||
"descr": "Запускает эффект",
|
||||
"params": []
|
||||
|
||||
},
|
||||
{
|
||||
"name": "pause",
|
||||
"descr": "Пауза эффекта",
|
||||
"params": []
|
||||
|
||||
},
|
||||
{
|
||||
"name": "resume",
|
||||
"descr": "Возобновляет эффект",
|
||||
"params": []
|
||||
|
||||
},
|
||||
{
|
||||
"name": "setSegment",
|
||||
"descr": "Устанавливает сегмент ленты",
|
||||
"params": [
|
||||
"номер сегмента от 0 до 7",
|
||||
"глобальный номер первого пикселя в сегменте",
|
||||
"глобальный номер последнего пикселя в сегменте",
|
||||
"номер эффекта от 0 до 79",
|
||||
"цвет в формате 0xRRGGBB, например, 0xFF0000 - красный, 0x00FF00 - зеленый, 0x0000FF - синий.",
|
||||
"скорость"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32*": [
|
||||
"adafruit/Adafruit NeoPixel @ ^1.12.5",
|
||||
"kitesurfer1404/WS2812FX @ ^1.4.5"
|
||||
],
|
||||
"esp82*": [
|
||||
"adafruit/Adafruit NeoPixel @ ^1.12.5",
|
||||
"kitesurfer1404/WS2812FX @ ^1.4.5"
|
||||
]
|
||||
}
|
||||
}
|
||||
125
src/modules/display/LedFX/Пример на 7 диодов.json
Normal file
125
src/modules/display/LedFX/Пример на 7 диодов.json
Normal file
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"mark": "iotm",
|
||||
"config": [
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "AnalogAdc",
|
||||
"id": "t1",
|
||||
"widget": "anydataRed",
|
||||
"page": "Сенсоры",
|
||||
"descr": "Аналог",
|
||||
"map": "1,1024,1,255",
|
||||
"plus": 0,
|
||||
"multiply": 1,
|
||||
"round": 1,
|
||||
"pin": 0,
|
||||
"int": "1",
|
||||
"avgSteps": 1
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "Variable",
|
||||
"id": "b1",
|
||||
"needSave": 0,
|
||||
"widget": "rangeServo",
|
||||
"page": "Сенсоры",
|
||||
"descr": "Бар 1",
|
||||
"int": "0",
|
||||
"val": "0.0",
|
||||
"map": "1024,1024,1,255",
|
||||
"plus": 0,
|
||||
"multiply": 1,
|
||||
"round": 0
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "Variable",
|
||||
"id": "b2",
|
||||
"needSave": 0,
|
||||
"widget": "rangeServo",
|
||||
"page": "Сенсоры",
|
||||
"descr": "Бар 2",
|
||||
"int": "0",
|
||||
"val": "0.0",
|
||||
"map": "1024,1024,1,255",
|
||||
"plus": 0,
|
||||
"multiply": 1,
|
||||
"round": 0
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "LedFX",
|
||||
"id": "fl20",
|
||||
"widget": "inputTxt",
|
||||
"page": "Кнопки",
|
||||
"descr": "Лента",
|
||||
"int": 15,
|
||||
"needSave": 0,
|
||||
"data_pin": "2",
|
||||
"numLeds": "7",
|
||||
"brightness": "50",
|
||||
"speed": "100",
|
||||
"color": "0xFF0000",
|
||||
"effectsMode": "11",
|
||||
"valueMode": 0,
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "VButton",
|
||||
"id": "pause",
|
||||
"needSave": 0,
|
||||
"widget": "toggle",
|
||||
"page": "Кнопки",
|
||||
"descr": "Пауза",
|
||||
"int": "0",
|
||||
"val": "0"
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "VButton",
|
||||
"id": "bars",
|
||||
"needSave": 0,
|
||||
"widget": "toggle",
|
||||
"page": "Кнопки",
|
||||
"descr": "Бары",
|
||||
"int": "0",
|
||||
"val": "0"
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "VButton",
|
||||
"id": "any",
|
||||
"needSave": 0,
|
||||
"widget": "toggle",
|
||||
"page": "Кнопки",
|
||||
"descr": "Анимация",
|
||||
"int": "0",
|
||||
"val": "0"
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"type": "Reading",
|
||||
"subtype": "VButton",
|
||||
"id": "fade",
|
||||
"needSave": 0,
|
||||
"widget": "toggle",
|
||||
"page": "Кнопки",
|
||||
"descr": "Скрыть",
|
||||
"int": "0",
|
||||
"val": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
scenario=>if bars then fl20.vuMeter(7, "t1") else fl20.stop()
|
||||
if any then fl20.setEffect(100) else fl20.stop()
|
||||
if pause then fl20.pause() else fl20.resume()
|
||||
if fade then fl20.fadeOut(1, 3) else fl20.fadeIn(60, 3)
|
||||
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)
|
||||
96
src/modules/display/U8g2lib/modinfo.json
Normal file
96
src/modules/display/U8g2lib/modinfo.json
Normal file
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"menuSection": "screens",
|
||||
"configItem": [
|
||||
{
|
||||
"name": "Экраны U8g2",
|
||||
"type": "Reading",
|
||||
"subtype": "U8g2lib",
|
||||
"id": "u8page",
|
||||
"widget": "",
|
||||
"page": "",
|
||||
"descr": "",
|
||||
|
||||
"oledType": "SS_I2C",
|
||||
"int": 1,
|
||||
"update": 500,
|
||||
"font": "c6x13",
|
||||
"contrast": 90,
|
||||
"rotation": 90,
|
||||
"autoPage": 1,
|
||||
"pageTime": 3000,
|
||||
"id2show": "",
|
||||
|
||||
"dc": 19,
|
||||
"cs": 5,
|
||||
"data": 23,
|
||||
"clock": 18,
|
||||
"rst": -1
|
||||
}
|
||||
],
|
||||
"about": {
|
||||
"authorName": "Ilya Belyakov",
|
||||
"authorContact": "https://t.me/Biveraxe",
|
||||
"authorGit": "https://github.com/biveraxe",
|
||||
"specialThanks": "Yuriy Trikoz @ytrikoz",
|
||||
"moduleName": "U8g2lib",
|
||||
"moduleVersion": "1.0",
|
||||
"usedRam": {
|
||||
"esp32_4mb": 15,
|
||||
"esp8266_4mb": 15
|
||||
},
|
||||
"moduleDesc": "Позволяет выводить на графические экраны типа SSD, ST, SH указанные параметры из конфигурации IoTM.",
|
||||
"propInfo": {
|
||||
"oledType": "Строковый код типа дисплея. В текущей верссии поддерживаются ST7565 (ST), SSD1306 (SS_I2C), SSD1306 (SS_SPI) и SH1106 (SH). Для получения списка доступных типов дисплеев, обратитесь к документации библиотеки U8g2. Добавить возможность выбора типов дисплеев можно, добавив соответствующие условия в файл модуля в конструктор класса U8g2lib.",
|
||||
"int": "Интервал обновления экрана в секундах. Если указано 0, то обновление экрана не производится.",
|
||||
"update": "Интервал обновления экрана в миллисекундах. Если указано 0, то обновление экрана не производится. (парамтер на развитие)",
|
||||
"font": "Шрифт, используемый для отображения текста на экране. Доступные шрифты можно найти в документации библиотеки U8g2 и добавить в проект в функцию setFont().",
|
||||
"contrast": "Контрастность экрана. Значение от 10 до 150, где 0 - минимальная контрастность, а 255 - максимальная.",
|
||||
"rotation": "Поворот экрана в градусах. Доступные значения: 0, 90, 180, 270.",
|
||||
"autoPage": "Автоматическая смена страниц экрана. Если установлено в 1, то экран будет автоматически переключаться на следующую страницу после указанного времени.",
|
||||
"pageTime": "Время в миллисекундах, через которое будет происходить автоматическая смена страниц экрана. Используется только если autoPage установлено в 1.",
|
||||
"id2show": "Идентификатор элемента конфигурации, значение которого будет отображаться на экране. Если указано, то на экране будет отображаться только это значение. Возможно указать несколько идентификаторов, разделенных запятыми для перечисления горизонтально и # для перевода строки.",
|
||||
"dc": "Пин, используемый для управления дисплеем по протоколу I2C. Если не используется, укажите -1.",
|
||||
"cs": "Пин, используемый для управления дисплеем по протоколу SPI. Если не используется, укажите -1.",
|
||||
"data": "Пин, используемый для передачи данных на дисплей по протоколу SPI. Если не используется, укажите -1.",
|
||||
"clock": "Пин, используемый для синхронизации данных на дисплее по протоколу SPI. Если не используется, укажите -1.",
|
||||
"rst": "Пин, используемый для сброса дисплея. Если не используется, укажите -1."
|
||||
},
|
||||
"title": "Дисплей U8g2lib",
|
||||
"funcInfo": [
|
||||
{
|
||||
"name": "nextPage",
|
||||
"descr": "Переключиться на следующую страницу",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"name": "prevPage",
|
||||
"descr": "Переключиться на предыдущую страницу",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"name": "rotPage",
|
||||
"descr": "Переключиться на следующую страницу с ротацией",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"name": "gotoPage",
|
||||
"descr": "Переключиться на указанную страницу. Если номер не указать, то переключится на страницу закрепленную за элементом конфигурации.",
|
||||
"params": ["Номер страницы"]
|
||||
},
|
||||
{
|
||||
"name": "setAutoPage",
|
||||
"descr": "Установить автоматическую смену страниц.",
|
||||
"params": ["1 - включить, 0 - выключить"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32*": [
|
||||
"olikraus/U8g2 @ ^2.36.5"
|
||||
],
|
||||
"esp82*": [
|
||||
"olikraus/U8g2 @ ^2.36.5"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,18 @@
|
||||
"descr": "Получаем количество секунд доверия к значениям элемента. При -2 доверие полное, при -1 время доверия истекло. При >0 время обратного отсчета. Используется только совместно с ИД элемента: ID.getIntFromNet()",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"name": "setInterval",
|
||||
"descr": "Меняем интервал выполнения периодиеских операций элемента в секундах. Используется только совместно с ИД элемента: ID.setInterval(5)",
|
||||
"params": ["Секунды"],
|
||||
"return": "установленный интервал"
|
||||
},
|
||||
{
|
||||
"name": "doByInterval",
|
||||
"descr": "Выполняем интервальное действие модуля вне плана. Используется только совместно с ИД элемента: ID.doByInterval()",
|
||||
"params": [],
|
||||
"return": "значение элемента после выполнения doByInterval"
|
||||
},
|
||||
{
|
||||
"name": "exit",
|
||||
"descr": "Прерываем работу сценария и выводим в консоль причину. Причина не обязательна.",
|
||||
|
||||
@@ -88,7 +88,7 @@ void* getAPI_AhtXX(String subtype, String param) {
|
||||
|
||||
if (ahts.find(addr) == ahts.end()) {
|
||||
int shtType;
|
||||
jsonRead(param, "type", shtType);
|
||||
jsonRead(param, "shtType", shtType);
|
||||
|
||||
ahts[addr] = new AHTxx(hexStringToUint8(addr), (AHTXX_I2C_SENSOR)shtType);
|
||||
|
||||
|
||||
@@ -103,6 +103,7 @@ uint8_t hexStringToUint8(const String& hex) {
|
||||
if (tmp >= 0x00 && tmp <= 0xFF) {
|
||||
return tmp;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t hexStringToUint16(const String& hex) {
|
||||
@@ -110,6 +111,15 @@ uint16_t hexStringToUint16(const String& hex) {
|
||||
if (tmp >= 0x0000 && tmp <= 0xFFFF) {
|
||||
return tmp;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t hexStringToUint32(const String& hex) {
|
||||
uint32_t tmp = strtol(hex.c_str(), NULL, 0);
|
||||
if (tmp >= 0x0000 && tmp <= 0xFFFFFF) {
|
||||
return tmp;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t itemsCount2(String str, const String& separator) {
|
||||
@@ -223,4 +233,29 @@ bool strInVector(const String& str, const std::vector<String>& vec) {
|
||||
if (vec[i] == str) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String getUtf8CharByIndex(const String& utf8str, int index) {
|
||||
if (index < 0) index = 0;
|
||||
|
||||
int len = utf8str.length();
|
||||
int charCount = 0;
|
||||
int i = 0;
|
||||
while (i < len) {
|
||||
int charLen = 1;
|
||||
unsigned char c = utf8str[i];
|
||||
if ((c & 0x80) == 0x00) charLen = 1; // 0xxxxxxx
|
||||
else if ((c & 0xE0) == 0xC0) charLen = 2; // 110xxxxx
|
||||
else if ((c & 0xF0) == 0xE0) charLen = 3; // 1110xxxx
|
||||
else if ((c & 0xF8) == 0xF0) charLen = 4; // 11110xxx
|
||||
|
||||
if (charCount == index) {
|
||||
return utf8str.substring(i, i + charLen);
|
||||
}
|
||||
|
||||
if (i + charLen >= len) return utf8str.substring(i, i + charLen);
|
||||
i += charLen;
|
||||
charCount++;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -11,7 +11,7 @@ from sys import platform
|
||||
pio_home = env.subst("$PROJECT_CORE_DIR")
|
||||
print("PLATFORMIO_DIR" + pio_home)
|
||||
|
||||
if platform == "linux" or platform == "linux2":
|
||||
if platform == "linux" or platform == "linux2" or platform == "darwin":
|
||||
# linux
|
||||
#mainPyPath = '/home/rise/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src/WiFiClient.cpp'
|
||||
mainPyPath = pio_home + '/packages/framework-arduinoespressif32/libraries/WiFi/src/WiFiClient.cpp'
|
||||
|
||||
@@ -7,7 +7,7 @@ from sys import platform
|
||||
pio_home = env.subst("$PROJECT_CORE_DIR")
|
||||
print("PLATFORMIO_DIR" + pio_home)
|
||||
|
||||
if platform == "linux" or platform == "linux2":
|
||||
if platform == "linux" or platform == "linux2" or platform == "darwin":
|
||||
# linux
|
||||
#devkitm = '/home/rise/.platformio/platforms/espressif32/boards/esp32-c6-devkitm-1.json'
|
||||
#devkitc = '/home/rise/.platformio/platforms/espressif32/boards/esp32-c6-devkitc-1.json'
|
||||
|
||||
@@ -9,7 +9,7 @@ from sys import platform
|
||||
pio_home = env.subst("$PROJECT_CORE_DIR")
|
||||
print("PLATFORMIO_DIR" + pio_home)
|
||||
|
||||
if platform == "linux" or platform == "linux2":
|
||||
if platform == "linux" or platform == "linux2" or platform == "darwin":
|
||||
#mainPyPath = '/home/rise/.platformio/platforms/espressif8266@4.0.1/builder/main.py'
|
||||
mainPyPath = pio_home + '/platforms/espressif8266@4.0.1/builder/main.py'
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user