Closed DAK85 closed 2 years ago
Есть другой способ немного не удобен как для новой версии но я начал её делать на старой в setup() пишешь
portal.server.on(String(PSTR("/setings")).c_str(),
SetingsPage);
SetingsPage() будет вызываться когда будет открыта страница /setings
void SetingsPage() {
portal.attachBuild(buildSetingsPage); // функция конструктора другой страницы
portal.show();
portal.attachBuild(build); // обратно ставим главную страницу которая будет вызываться любым (например /home)
}
//кроме раньше зазначеных в portal.server.on
// можно также не ставить build если нужно чтобы главная страница открывалась ТОЛЬКО по какой-то ссылке
Согласен выглядит как небольшой изврат, но очень легко редактировать нужный конструктор страницы
Это слишком заморочено, я пока просто portal объявил глобально. возможно пока так всё и останется. Ну вот в обработчик событий можно передать локально объявленный портал, а вот как в конструктор его передать? по факту вроде ничего сложного не должно быть, я немного запутался в коде библиотеки, так бы уже подправил себе да жил спокойно.
В следующем обновлении сделаю билдер с возможностью передать ссылку на объект портала. А вообще - можно через лямбда функцию сделать
void f() {
GyverPortal portal;
portal.attachBuild([](){
// Бла бла сборка
});
}
String(PSTR("/setings")).c_str()
Это что за покемон?)))) Достаточно сделать F("/строка"), см. урок https://alexgyver.ru/lessons/progmem/
Функция on
принимает любые строки, не нужен ей char*
И кстати String(PSTR("/setings"))
делать НЕЛЬЗЯ, по тем же причинам что вчера обсуждали. Можно через FPSTR() - String(FPSTR(PSTR("/setings")))
. Но именно это и делает F()
Я вот одного понять не могу, вот есть GP.FORM_BEGIN("uri"), по факту этот ури должен совпадать с той страницей где сейчас находишься (так в документации написано), напрашивается сделать прямо вот так GP.FORM_BEGIN(portal.uri()) для удобства, но нет, uri вернёт стринг, её туда просто так не засунешь... Я вроде много уроков перечитал, но из стринга чар сделать одним движением не получается (может я чего то не понимаю), очень не удобно при создании страниц динамикой.
но из стринга чар сделать одним движением не получается
str.c_str()
В следующей версии будет гораздо удобнее, выйдет в течение пары дней
А по поводу ещё пары хотелок можно написать? просто так, предложения по улучшению?
Естественно, самое время, пока обновление не вышло)
SELECT бы доделать немного, сейчас в в селект value=name, что дико неудобно, хочется отдавать иначе.... я не программист, просто иногда бывает надо... я дописал себе такую штуку
void SELECT_2ARRAY(const char* name, byte* values, char* namevalues, int x, int y, int8_t maxcount, int sel = 0 ) {
char temp_array[x][y];
for (int xi=0;xi<x;xi++){
for (int yi=0;yi<y;yi++){
temp_array[xi][yi]=namevalues[xi*y+yi];
}
}
if (sel < 0) sel = 0;
*_GP += F("<select name=\"");
*_GP += name;
*_GP += F("\" id=\"");
*_GP += name;
*_GP += F("\" onchange=\"GP_click(this)\">\n");
for(uint8_t count = 0; count < maxcount;count++){
*_GP += F("<option value=\"");
*_GP += values[count];
*_GP += F("\"");
if (values[count] == sel) *_GP += F(" selected");
*_GP += F(">");
*_GP += count;
*_GP += F(". ");
*_GP += temp_array[count];
*_GP += F("</option>\n");
}
*_GP += F("</select>");
}
В селект уходит ссылка на массив с значениями и с именами, у меня имена в двумерном массиве char, объявляю глобально, например char name[4][20]. В конструктор отдаю указатель (может путаю) на массив, и его размер. Может я что то делаю не так, но главное принцип, прямыми руками это можно сделать в разы лучше. Как пример, я в конструкторе вызываю следующим образом
GP.SELECT_2ARRAY("addressRelay",AddressModule,*NameModule,MaxModuleCount,NameModuleLenght,ModuleCount,addressRelay[idrelay])
Последний аргумент, это значение, которое надо сделать selected, предпоследнее - это количество элементов в списке.
Так же сделал вариант, когда значение от 0 по нарастающей
void SELECT_ARRAY(const char* name, char* namevalues, int x, int y, int8_t maxcount, int8_t sel = 0 ) {
char temp_array[x][y];
for (int xi=0;xi<x;xi++){
for (int yi=0;yi<y;yi++){
temp_array[xi][yi]=namevalues[xi*y+yi];
}
}
if (sel < 0) sel = 0;
*_GP += F("<select name=\"");
*_GP += name;
*_GP += F("\" id=\"");
*_GP += name;
*_GP += F("\" onchange=\"GP_click(this)\">\n");
for(uint8_t count = 0; count < maxcount;count++){
*_GP += F("<option value=\"");
*_GP += count;
*_GP += F("\"");
if (count == sel) *_GP += F(" selected");
*_GP += F(">");
*_GP += count;
*_GP += F(". ");
*_GP += temp_array[count];
*_GP += F("</option>\n");
}
*_GP += F("</select>");
}
И да, храню настройки в flash памяти в файлах (), использую spiffs, хочется сделать возможность загрузки и скачивания конфигурации. Может это и возможно, но я пока не понял как это реализовать.
Не очень понял в чём отличие и зачем. Можно отличия в HTML эквиваленте? И почему что дико неудобно
и в каких случаях.
возможность загрузки и скачивания конфигурации
Очень удобный механизм загрузки и скачивания будет в следующем обновлении
В текущей реализации получается выглядит так:
<select name='sel' id='sel' onchange='GP_click(this)'>
<option value='val 1'selected>val 1</option>
<option value='val 2'>val 2</option>
<option value='val 3'>val 3</option>
</select>
Это как из базы выбрать по индексу. Хранишь значение, а при настройке выбираешь имя. Выбор из справочника так сказать.
<select name='sel' id='sel' onchange='GP_click(this)'>
<option value='1'selected>1. Иванов</option>
<option value='2'>2. Петров</option>
<option value='3'>3. Сидоров</option>
</select>
Выбираешь Сидорова, а в sel уходит 3
Согласен. Не помню почему не сделал так раньше, может были причины. Я тогда сделаю с автоматическим счётчиком, будет универсально и не такая жуткая функция получится, сейчас попробую
автоматический счётчик это хорошо, но бывает надо скажем взять значение из одного массива byte address[x], а имена из другого массива char name[x][y]. как бы получается что счётчик (i) идёт от 0 до maxcount, значения точно числовые, а вот имена чаще всего текстового формата.
Я не очень понял зачем тут временный массив и как он должен работать. Получается массив то char
, а передаются в него адреса строк. В любом случае идея хорошая, в том или ином виде добавлю в релиз, спасибо
я же в временный массив передаю через указатель значения из глобально определённого массива, а глобальный массив двумерный по факту я внутри функции делаю новый массив char (копия глобального)
но зачем, можно ведь прямо из него к строке прибавлять)
String(PSTR("/setings")).c_str()
Это что за покемон?)))) Достаточно сделать F("/строка"), см. урок https://alexgyver.ru/lessons/progmem/ Функция
on
принимает любые строки, не нужен ейchar*
И кстати
String(PSTR("/setings"))
делать НЕЛЬЗЯ, по тем же причинам что вчера обсуждали. Можно через FPSTR() -String(FPSTR(PSTR("/setings")))
. Но именно это и делаетF()
Просто так получилось, PSTR() не работал там я просто String добавил и не заморачивался)
Это потому что я не умею этого делать, поэтому пишу тому, кто умеет! Честно! я наверное скоро и это научусь, но пока ещё не могу Ну раз пошла такая пьянка... вот есть GP_NUMBER, хочется чтобы подсказка была вида
#define MaxCount 40
GP.NUMBER("RoomsCount", "Введите значение менее", MaxCount, RoomsCount)
Ну это так, просто хотелки, замучался я с преобразованием переменных....
Это потому что я не умею этого делать, поэтому пишу тому, кто умеет!
Ну ты ведь читаешь из строки в строку. Почему просто не читать сразу из изначальной строки в *GP?)
замучался я с преобразованием переменных....
Не понял ничего)
Кстати раз пошли предложения я зделал мини библиотеку https://github.com/DenysChuhlib/GyverPortalAddon с деталаями которых нету. Надеюсь вам что-то приглянётся)
мини библиотеку
Во, хорошая идея для создания дополнений. Но к сожалению со следующего обновления это перестанет работать, я полностью переделал бэкэнд конструктора...
Что-то придумаю)
Не понял ничего)
Подсказка должна быть передана в const char*, надо сперва выдергнуть число, переделать его в чар, потом подцепить к тексту подсказки, и только потом передать подсказку в GP.NUMBER, либо ткните меня носом в уроки, хотя я их наверное все перечитал
Подсказка должна быть передана в const char*
Зачем, можно просто в char*
. Стринг жрёт почти всё. По урокам мои вот, недавно обновлялись:
Сделать можно было так:
void SELECT_2ARRAY(const char* name, byte* values, char** namevalues....
s += namevalues[count];
char *names[] = {"kek1","kek2","kek3","kek4",}; // а лучше const char
И всё
передать подсказку в GP.NUMBER
в следующей версии все функции конструктора принимают const String&
, так что можно будет изи передавать любые данные через String(блабла)
дело в том, что у меня строки разной длинны, может быть 3 буквы, может 10... Я бы так и читал, const не могу, имена можно менять прямо с телефона, как понять что имя 3 буквы а не 10, не 16 (ещё и русские буквы)
Может так тогда
while (namesvalue[count]!=0){
s+=namesvalue[count];
count++;
}
После перескока на новое слова count увеличивать пропорционально длине строки...
В том и дело, что это неважно. Строка оканчивается нулевым символом, программа сама определяет ее длину и прибавляет к стринге
я догнал! char**, это подразумевает двумерный массив, а count как раз его "ширину", гениально.... а как функцию вызывать?
А вот подобного метода не будет в новой библиотеки?
bool formSub(const String& name) {
return _formF ? (_uri.startsWith(name) && (_uri.lastIndexOf("GP_click")==-1)) : 0;
}
И главный вопрос, функции в классе так же легко будет переделывать под свои нужды?
count как раз его "ширину"
Не совсем так. Для программы строка - это всего лишь указатель на первый символ. Программа не знает длины строки, но знает что она заканчивается /0
. Массив names
выше - это массив адресов первых символов строк, но передать его придется как **
. Дальше думаю логика понятна.
подобного метода
Не понял что это и зачем
так же легко
По сути даже легче, самое главное что памяти драматически меньше будет жрать
Просто когда я захожу в пункт /set/relay у меня циклом создаётся около 40 BUTTON_LINK, когда я в них проваливаюсь у меня ури /set/relay/NUMBER (как пример /set/relay/10), там открывается форма, соответственно у неё точно такой же ури. Когда я в action проверяю события мне надо ловить все события с форм, которые начинаются на /set/relay/, как только у меня срабатывает это условие, я через formName() получаю uri строку, вычлиняю из неё последний индекс (10) и далее работаю с ним обновляя массивов по полученному индексу
А разве недостаточно просто передать полный урл в form()
? По поводу клика - в обновлении вызов формы игнорируется, если не переданы аргументы, то есть кнопка-ссыдка не приведет к триггеру обработки формы
Немного бреда, но может поймёте мою беду:
void build() {
BUILD_BEGIN();
GP.THEME(GP_DARK);
//************Тут меню настроек
if (portal.uri()=="/settings"){
GP.BUTTON_LINK("/settings/system", "Системные настройки"); GP.BREAK();
GP.BUTTON_LINK("/settings/rooms", "Настройки комнат"); GP.BREAK();
GP.BUTTON_LINK("/settings/module", "Настройки модулей"); GP.BREAK();
GP.BUTTON_LINK("/settings/button", "Настройки кнопок"); GP.BREAK();
GP.BUTTON_LINK("/settings/relay", "Настройки реле"); GP.BREAK();
GP.BUTTON_LINK("/settings/group", "Настройки групп"); GP.BREAK();
GP.BUTTON_LINK("/settings/timer", "Настройки таймеров"); GP.BREAK();
GP.BUTTON_LINK("/settings/saveload", "Загрузка/Сохранение"); GP.BREAK();
GP.BUTTON_LINK("/", "Назад");
//************Тут системные настройки
} else if (portal.uri()=="/settings/system") {
GP.TITLE("Системные настройки");
GP.FORM_BEGIN("/settings/system");
GP.LABEL("Количество комнат:");GP.NUMBER("RoomsCount", MaxRoomsCount, RoomsCount);GP.BREAK();
GP.LABEL("Количество модулей:");GP.NUMBER("ModuleCount", MaxModuleCount, ModuleCount);GP.BREAK();
GP.LABEL("Количество реле:");GP.NUMBER("RelayCount", MaxRelayCount, RelayCount);GP.BREAK();
GP.LABEL("Количество групп:");GP.NUMBER("GroupCount", MaxGroupCount, GroupCount);GP.BREAK();
GP.LABEL("Количество реле в группе:");GP.NUMBER("GroupRelayCount", MaxGroupRelayCount, GroupRelayCount);GP.BREAK();
GP.LABEL("Количество таймеров:");GP.NUMBER("TimerCount", MaxTimerCount, TimerCount);GP.BREAK();
GP.LABEL("Количество отслеживаемых реле:");GP.NUMBER("TimerRelayCount", MaxTimerRelay, TimerRelayCount);GP.BREAK();
GP.LABEL("Количество выходов на кнопки:");GP.NUMBER("OutputPinButtonCount", MaxOutputPinButtonCount, OutputPinButtonCount);GP.BREAK();
GP.LABEL("Количество входов на кнопки");GP.NUMBER("InputPinButtonCount", MaxInputPinButtonCount, InputPinButtonCount);GP.BREAK();
GP.SUBMIT("Применить");
GP.FORM_END(); GP.BREAK();
GP.BUTTON_LINK("/settings", "Назад");
//************Тут кнопки с названием комнат
} else if (portal.uri()=="/settings/rooms") {
for (int h=0;h<RoomsCount;h++){
GP.BUTTON_LINK("/settings/rooms/", NameRooms[h], h, true); GP.BREAK();
}
GP.BUTTON_LINK("/settings", "Назад");
//************Тут форма на редактирование имени комнаты
} else if (portal.uri().startsWith("/settings/rooms/")) {
int idroom=portal.uri().substring(portal.uri().lastIndexOf("/")+1).toInt();
GP.TITLE("Настройка комнат");
GP.LABEL("Номер комнаты:");GP.LABEL(idroom);GP.BREAK();
GP.FORM_BEGIN(portal.uri());
GP.TEXT("NameRooms", "Введите имя комнаты", NameRooms[idroom]);GP.BREAK();
GP.SUBMIT("Применить");
GP.FORM_END(); GP.BREAK();
GP.BUTTON_LINK("/settings/rooms", "Назад");
//************Тут кнопки с названием модулей
} else if (portal.uri()=="/settings/module") {
for (int h=0;h<ModuleCount;h++){
GP.BUTTON_LINK("/settings/module/", NameModule[h], h, true); GP.BREAK();
}
GP.BUTTON_LINK("/settings", "Назад");
//************Тут формы для редактировния параметров модулей
} else if (portal.uri().startsWith("/settings/module/")) {
int idmodule=portal.uri().substring(portal.uri().lastIndexOf("/")+1).toInt();
GP.TITLE("Настройка Модуля");
GP.LABEL("Номер модуля:");GP.LABEL(idmodule);GP.BREAK();
GP.FORM_BEGIN(portal.uri());
GP.TEXT("NameModule", "Введите имя модуля", NameModule[idmodule]);GP.BREAK();
GP.NUMBER("AddressModule", "Введите адрес модуля", AddressModule[idmodule]);GP.BREAK();
GP.SUBMIT("Применить");
GP.FORM_END(); GP.BREAK();
GP.BUTTON_LINK("/settings/module", "Назад");
...................
Так просто удобнее, иначе вот как, изначально брать имя формы потом его анализировать.... Это и так только начало, будет раза в 4 больше....
upd: Не судите, часть функций в библиотеки дописал чтобы стало хоть немного изи
Не то немного вставил, вот актион
if (portal.formSub("/settings/rooms/")){
int idroom=portal.formName().substring(portal.formName().lastIndexOf("/")+1).toInt();
portal.getString("NameRooms").toCharArray(NameRooms[idroom],NameRoomsLenght);
}
if (portal.formSub("/settings/module/")){
int idmodule=portal.formName().substring(portal.formName().lastIndexOf("/")+1).toInt();
AddressModule[idmodule]=portal.getInt("AddressModule");
portal.getString("NameModule").toCharArray(NameModule[idmodule],NameModuleLenght);
}
if (portal.formSub("/settings/relay/")){
int idrelay=portal.formName().substring(portal.formName().lastIndexOf("/")+1).toInt();
pinRelay[idrelay]=portal.getInt("pinRelay");
addressRelay[idrelay]=portal.getInt("addressRelay");
RoomRelay[idrelay]=portal.getInt("RoomRelay");
portal.getString("NameRelay").toCharArray(NameRelay[idrelay],NameRelayLenght);
}
if (portal.formSub("/settings/group/")){
int idgroup=portal.formName().substring(portal.formName().lastIndexOf("/")+1).toInt();
GroupRelay[idgroup][0]=portal.getInt("GroupRelayCount");
for(int idgrouprelay=1;idgrouprelay<=GroupRelay[idgroup][0];idgrouprelay++){
char textnamerelay[8];char idstr[2];itoa(idgrouprelay,idstr,DEC);strcpy(textnamerelay,"relay@");strcat(textnamerelay,idstr);
GroupRelay[idgroup][idgrouprelay]=portal.getInt(textnamerelay);
}
Так просто реально удобно
и ещё, когда я делаю клик на кнопке на странице uri:/settings/saveload Событие клика приходит со страницы uri:/settings/GP_click соответственно тот метод, который сейчас у Вас в библиотеки не видит этого события, об этом я уже писал в самом начале
В самом начале до конца не прочитал, думал там всё про проблему локального портала. Гляну
Наверное хватит отнимать Ваше время! Спасибо за Ваши труды! И уроки классные, сайт просто Огонь, форум читаю, телегу смотрю, когда на всё находите время я даже не представляю... Здоровья, процветания! Отдельно спасибо за уделённое время!
Всегда пожалуйста)
так, не понял проблему с формами. Вот есть у меня
if (portal.uri() == "/save/sub") {
GP.FORM_BEGIN("/save/sub");
GP.SUBMIT("Submit");
GP.FORM_END();
}
И проверяю как
if (portal.form("/save/sub"))
Работает же
Ну это понятно что работает, а если у тебя генерируется /save/sub/key@0, /save/sub/key@1, /save/sub/key@2 ... /save/sub/key@N, тогда надо сперва дёргать название формы, потом проверять на что оно заканчивается или на что начинается, выдёргивать ID, а можно просто сделать в action написать
if (portal.formSub("/save/sub/key")){
int idkey=portal.formName().substring(portal.formName().lastIndexOf("/key@")+5).toInt();
И это условие сработает если из любой формы, имя которой начинается на "/save/sub/key" прилетит submit
А в твоём варианте надо будет изначально брать адрес формы через formName() и потом делать условие не начинается ли имя пришедшей формы с "/save/sub/key". Просто удобнее, но можно и обойтись.... Я уже думаю на тему того, что проще в action один раз выдергнуть имя формы и потом его гонять по if else if else if....
Теперь понял. Нужно чтобы одно условие сработало от всех форм, находящихся на указанном урл или глубже. По сути на фронте это можно сделать как
if (form() && uri.indexof blabla)
?
можно и так, только наверное if (form() && uri.startsWith blabla), эффект наверное будет тот же... Я ни на что не претендую, я с нетерпением жду новой версии библиотек, обновлюсь и буду пытаться всё сделать без редактирования кода библиотеки. Если появится select из двух массивов и плюс ко всему события GP_Click с вложенных страниц будут ловится хорошо, то мне больше ничего и не надо... Просто сам факт загрузки фалов и передачи в build экземпляра локально объявленного GyverPortal это уже отличная новость! Ждёмс релиза!
Да, остальное всё я уже сделал. Только по форме остался вопрос, просто не хочется добавлять функцию, к которой сложно придумать простой пример. Ну и раз оно заменяется полностью конструкцией из других стандартных функций, то можно уже не добавлять) я могу рабочую версию дать пощупать, может ещё какие косяки всплывут пока я примеры и доку обновляю
а я бы не отказался пощупать, посмотреть!
Сколько у меня времени?
Доброго времен суток
(версия библиотеки 2.1.0)
else if (_uri.startsWith(F("/GP_click")))"
на"else if (_uri.indexOf(F("/GP_click"))!=-1)"
думаю с апдейтами будет та же проблема.// вернёт true, если был submit с форм, имя которых начинмется с name
bool formSub(const String& name) {
return _formF ? (_uri.startsWith(name) && (_uri.lastIndexOf("GP_click")==-1)) : 0; }
теперь чтобы поймать события с вложенных страниц надо просто вызвать portal.formSub("/settings/system/relay/") Думаю что это полезно, однако не уверен как поведёт себя при апдейтах, возможно надо дописать исключение событий GP_update, я не использую, мне этого хватает.Пока для меня критичен самый первый вопрос, я совсем не программист и пока "не догнал" как передать "portal в build()"
А вообще очень отличная библиотека, правда я в неё дописал пару функций, но в этом её и прелесть, всё понятно, всё читается, можно спокойно дописывать свои функции в конструктор! Спасибо за такую находку!