Merge pull request #41 from IoTManagerProject/ver4dev

Ver4dev
This commit is contained in:
Mit4el
2025-07-25 00:06:32 +03:00
committed by GitHub
20 changed files with 2181 additions and 10 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
View 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()

View File

@@ -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> &param) {
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

View File

@@ -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());
}
}

View 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> &param) {
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;
}
}

View 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"
]
}
}

View 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)

Binary file not shown.

View 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;
}
};

View 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;
}
}

View 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)

View 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"
]
}
}

View File

@@ -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": "Прерываем работу сценария и выводим в консоль причину. Причина не обязательна.",

View File

@@ -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);

View File

@@ -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 "";
}

View File

@@ -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'

View File

@@ -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'

View File

@@ -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: