Перейти к публикации
iT4iT.CLUB

Рекомендованные сообщения

@Kitsum спасибо, поизучаю.

PS, да да да, именно такая у меня память, перепаяю и попробую снова. спасибо!

Изменено пользователем pasha413

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
13.10.2018 в 14:09, Kitsum сказал:

PS: Забыл упомянуть. Я бы советовал Вам не выводить на дисплей данные сразу после их поступления. Лучше их накапливать. Далее в планировщик задач добавить функция вызываемую, например, каждую минуту и проверяющую отличаются ли поступившие данные от тех, что уже выведены на дисплей. Если да, то выводить новые данные. Таким образом Вы будите экономит процессорное время и избавите контроллер от лишних задач по выводу одинаковых данных на дисплей.

Добрый день. Все же не до конца понимаю.  client.subscribe - функция по чтению или подписке на данные. Как сделать универсально для запроса данных.

Сделал по Вашему научению так:

void callback(char* topic, byte* payload, unsigned int length) {
     #ifdef console
      console.println(F("services: callback data from MQTT server"));
    #endif 
}
void readDataFromMQTT() {
    // баг при прямой передаче значения (c_str) из конфига в setServer (не забыть поправить!)
    String server = conf.param("mqtt_server");
    mqttAPI.setServer(server.c_str(), 14728); //1883
    mqttAPI.connect(WiFi.hostname().c_str(),
      (conf.param("mqtt_login").length() ? conf.param("mqtt_login").c_str() : 0),
      (conf.param("mqtt_pass").length() ? conf.param("mqtt_pass").c_str() : 0)
    );  
    mqttAPI.setCallback(callback);
    if (!mqttAPI.connected() and wifi.transferDataPossible()) {
        if(mqttAPI.connect(WiFi.hostname().c_str())) {
            mqttAPI.subscribe("Odincovo/TVOC");
        }
    }
}
  cron.add(cron::time_5m, sendDataToMQTT);       // Отправка данных MQTT брокеру
  cron.add(cron::time_5m, readDataFromMQTT);       // Получение данных MQTT брокеру

Нужно ли каждый раз передавать настройки сервера и сети?

Можно ли сделать функцию числовую 

float readDataFromMQTT(string topic)

Что чтение будет это понятно, не могу понять как вытащить данные и передать дальше?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

@Devilisimo доброе время суток. Боюсь, что у Вас все перемешалось. Давайте разбираться подробнее.

В первую очередь необходимо понять, что из себя представляет MQTT протокол.

Работает он по схеме "издатель-подписчик" и подразумевает, что подписчик после подключения передает брокеру (центральному сервере) список интересующих его топиков. После данной операции подписчик переходит в режим ожидания в котором только поддерживает связь с брокером. После того как в каком-либо топике происходит публикация сообщения, центральный сервер берет список всех клиентов, подписанных на него и сообщает им об изменениях. Подписчикам приходит оповещения, ради которого они и поддерживали связь.

Если смотреть со стороны издателя, то поддерживать постоянную связь нет необходимости. Когда требуется опубликовать новые данные издатель подключается к брокеру и передает адрес топика и полезную нагрузку. Далее просто отключается.

Таким образом издатели и подписчики совершенно не знают ничего друг о друге. Да им это и не требуется. Всем занимается центральный сервер. Изначально данный проект метеостанции работает с MQTT протоколом только в роли издателя.

Как вижу решения я

В первую очередь создаем функция вызываемую при публикации сообщений в топиках, на которые мы подписаны.

void callback(char* topic, byte* payload, unsigned int length) {
  String msg;
  for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
  #ifdef console
    console.printf("\nСообщение[%s]: %s\n", topic, msg.c_str());
  #endif
}

Описать её можно в файле services.h, по хорошему её нужно назвать иначе, но мы уже начали использовать это имя поэтому я буду его придерживаться.

У нас есть обработчик, теперь необходимо его задействовать. Для большего порядка создадим дополнительную функцию.

void setupMQTT() {
  if (conf.param("mqtt_server").length()) {
    /* Указываем сервер и порт для подключения. Сервер можно указать через WEB интерфейс, порт измените на свой. */
    mqttServer = conf.param("mqtt_server");
    mqttAPI.setServer(mqttServer.c_str(), 1883);
  
    /* Устанавливаем обработчик */
    mqttAPI.setCallback(callback);  
  
    /* Задача для восстановления соединения */
    cron.add(cron::time_1m, [&](){
      if (!mqttAPI.connected() and wifi.transferDataPossible()) {
        if(mqttAPI.connect(WiFi.hostname().c_str(),
          (conf.param("mqtt_login").length() ? conf.param("mqtt_login").c_str() : 0),
          (conf.param("mqtt_pass").length() ? conf.param("mqtt_pass").c_str() : 0)
        )) {
          /* Список топиков на которые необходимо оформить подписку */
          mqttAPI.subscribe("test/esp8266/work");
        }
        #ifdef console
          console.printf("Connecting to MQTT server\nanswer: %s\n", mqttCodeStr(mqttAPI.state()).c_str());
        #endif
      }
    }, false);        
  }
  /* end if */
}

Думаю, тут все понятно. Функция описывает к какому серверу подключаться, устанавливает обработчик и добавляет в планировщик задачу для подключения к серверу в случае разрыва связи.

В функции используется переменная объявленная в глобальном пространстве имен.

String mqttServer;

Объявить переменную можно прямо в файле services.h

Вынос её за пределы функции обусловлен необходимостью сохранения имени MQTT сервера т.к библиотека PubSubClient использует обращается к ней через указатель. Все это очередное напоминание о необходимости доработки модуля хранения настроек конфигурации микроконтроллера.

Данную функцию мы будем единожды вызывать при старте контроллера поэтому добавим её в конец функции setup в основном файле.

void setup() {
  /* ... */
  
  setupMQTT();
}

Но этого все еще недостаточно чтобы мы начали получать сообщения. Необходимо добавить обработчик MQTT для завершения реализации режим "подписчика".

void loop() {
  /* ... */
  
  /* Обработчик MQTT для режима "подписчика" */
  mqttAPI.loop();
}

Можно считать основную работу законченной, НО мы помним, что ранее ESP работал только в режиме издателя, а это значит, что мы можем попасть на ошибки, связанные с добавлением нового режима. Давайте все перепроверим и избавимся от них. Идем в функцию, отвечающую за публикацию данных с сенсоров метеостанции и удаляем из ней все, что связанно с подключением к брокеру т.к теперь эта задача возложена на плечи планировщика задач.

/* mqtt.it4it.club */
void sendDataToMQTT() {
  if (wifi.transferDataPossible() and conf.param("mqtt_server").length() and mqttAPI.connected()) {
    #ifdef console
      console.println(F("services: send data to MQTT server"));
    #endif
      
    mqttPublish("light",            sensors.get("out_light"));
    mqttPublish("temperature",      sensors.get("out_temperature"));
    mqttPublish("humidity",         sensors.get("out_humidity"));
    mqttPublish("pressure",         sensors.get("out_pressure"));
    mqttPublish("co2",              sensors.get("out_co2"));
    mqttPublish("absoluteHumidity", sensors.get("out_absoluteHumidity"));

    #ifdef console
      console.printf("answer: %s\n", mqttCodeStr(mqttAPI.state()).c_str());
    #endif
  }
}

Также обязательно убираем вызов метода отвечающего за разрыв соединения с брокером.

mqttAPI.disconnect();

А в основное условие с проверками возможности передачи данных добавляем проверку наличия подключения к MQTT брокеру.

mqttAPI.connected();

На этом можно считать модернизацию законченной.

Я не описывал подробности реализации функции callback о которых говорил в предыдущих постах т.к это напрямую зависит от потребностей каждого, но напоминаю о варианте с накоплением данных и выводом их на дисплей через планировщик задач.

PS: если в ваших модификация для проекта присутствует дополнительный код, связанный с работой MQTT протокола, он должен быть упразднен или модифицирован в соответствии с описанной реализацией механизма "издатель-подписчик". Говорю об этом т.к в теме уже есть упоминания о таких модификациях от других форумчан и их использование будет приводить к постоянному разрыву соединения. Ну и как всегда, все проверяйте, т.к мои мысли могут быть ошибочны.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
5 часов назад, Devilisimo сказал:

Можно ли сделать функцию числовую

Это отдельная история, связанная с реализацией функции callback. Если Вы безоговорочно доверяете пришедшим данным от брокера, то в контексте описанной ранее функции можно преобразовать String в Int с помощью метода toInt

msg.toInt();

Но так лучше не делать, особенно в интернете.

Я бы посоветовал составить список дополнительных проверок для каждого интересующего Вас топика. При этом проверки должны быть не только связаны с типом данных, но и с их значением, особенно если контроллер управляет внешними устройствами.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Сделал по Вашем рекомендациям.

void callback(char* topic, byte* payload, unsigned int length) {
    String msg = "M=";
    for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];

    char measurementa [12];
    msg.toCharArray(measurementa, 12);
    u8g2.setFont(u8g2_font_inb19_mf);
    u8g2.drawStr((128 - u8g2.getStrWidth(measurementa))/2, u8g2.getAscent() - u8g2.getDescent(), measurementa);
    u8g2.sendBuffer();
                   
     #ifdef console
      console.println(F("services: callback data from MQTT server"));
    #endif 
}

Пока сделал тупо для вывода на экран. Ничего не получилось, хотя данные на броккер отправляются.

          /* Список топиков на которые необходимо оформить подписку */
          mqttAPI.subscribe("Odincovo/TVOC");
          mqttAPI.subscribe("Odincovo/BME280_out_temp");

Еще вопрос как будут разделены данные при подписке на несколько топиков.

          /* Список топиков на которые необходимо оформить подписку */
          mqttAPI.subscribe("weather_station/lcd/topic_name");

Почему Вы указываете название топика с двумя "/", т.е. может я как-то не так указываю адрес, хотя не телефоне все отображается?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

@Devilisimo еще раз доброе время суток. Озадачился я Вашим последним вопросом, пришлось найти микроконтроллер и все протестировать в живую.

Что получилось в итоге - была выявлена проблема хранения адреса MQTT сервера. Точнее даже не проблема, просто моя не внимательность т.к библиотека PubSubClient обращается к переменной с адресом через указатель то хранить данную переменную в функции setupMQTT не представляется возможным из-за освобождения памяти после ей выполнения. Соответственно, к тому моменту, как планировщик переходит к заданию по контролю соединения с брокером, адреса сервера уже не существует. Я упустил этот момент, когда подгонял в голове работу издателя и подписчика в рамках services.h

Что по существу изменилось, так это перенос переменной с именем сервера в глобальное пространство имен. Я обновил предыдущий свой пост и указал на этот факт чтобы другие тоже могли воспользоваться этой инструкцией. Код функций может чуть отличаться, например, именем топика, т.к я взял его из кода залитого для теста в микроконтроллер. Все проверил на живом контроллере и получил положительный результат.

15.10.2018 в 16:29, Devilisimo сказал:

Пока сделал тупо для вывода на экран. Ничего не получилось, хотя данные на броккер отправляются.

Ошибка, скорее всего, была связана с не невозможностью подключиться к серверу.

15.10.2018 в 16:29, Devilisimo сказал:

Еще вопрос как будут разделены данные при подписке на несколько топиков.

Всегда будет вызываться функция, указанная в качестве аргумента для метода setCallback. В функцию будут переданы параметры, среди которых имя топика и его новое значение. Отталкиваясь от этих данных Вы можете сделать обработку для каждого топика.

15.10.2018 в 16:29, Devilisimo сказал:

Почему Вы указываете название топика с двумя "/", т.е. может я как-то не так указываю адрес, хотя не телефоне все отображается?

Вы можете указывать практически сколько угодно субтопиков в имени топика. Например, использовать топик с полным физическим адресом и указанием страны, города, улицы и т.д. Слэш в данном случае играет роль разделителя и не более того.

Вот как это выглядит на практике.

mqtt-rw.jpg

Синим выделены отправленное на брокер mqtt.it4it.club в топик test/esp8266/work сообщение.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Все супер, заработало.

Еще нужна небольшая подсказка. Хочу сделать универсальную подписку:

            switch2 = "switch/" + conf.param("mqtt_path") + "sw2";
            mqttAPI.subscribe(switch2);

Ругается: no matching function for call to 'PubSubClient::subscribe(String&)'

Какого типа должна быть переменная?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
3 часа назад, Devilisimo сказал:

Все супер, заработало.

Не поделитесь с народом вашим успехом?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

ESP8266_WS_V2.0_iT4IT.CLUB.7z

Если кому-то интересно выкладываю свою версию, она пока сырая, но работает.

Начал строить свой "умный" дом, броккер установил на малинку, рулим всем через Node-red.

Очень удобно. Пока допиливаю управление gpio через mqtt.

Хотел спросить у автора - так понимаю реализована функция, что если нет вай-фая, то переходит в режим точки доступа. Соответственно все данные по старому подключению стираются? Если дома отключился свет или еще что-то. Если мы хотим только через одно подключение делать все, то надо жестко его будет прописать?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
09.10.2018 в 10:10, Kitsum сказал:

@post125 Это хороший и довольно интересный вариант.

Вам будут доступны несколько интерфейсов для связи. Разработчик заложил связь через UART, но Вы вполне можете связать ATmega 2560 и ESP8266 через I2C, порты GPIO4 и GPIO5 доступны и выведены на колодку #1.

image.png

Указано, что на плате распаяна флешь на 32 мегабайта, это хорошо т.к на старых ревизиях с 1-м мегабайтом было бы совсем грустно и пришлось бы облегчать оформление web интерфейса ESP.

Из особенностей отмечу, что управление внешними исполнительными механизмами полностью ложиться на ATmega 2560, если вообще этот функционал будет востребован.

Благодарю за ответ,

1. с сегодняшнего дня решил начать пробовать программировать ESP, взял Вашу стандартную прошивку, подсоединил поддержку ESP в Arduino IDE, выделил 3Мб под SPIFFS. залил "ESP8266 Sketch Data Upload". а воот надо ли нажимать кнопку обычной загрузки скетча? у меня выдает ошибку - с этим разобрался, а вот теперь пишет другие ошибки:

Arduino: 1.8.5 (Mac OS X), Плата:"NodeMCU 1.0 (ESP-12E Module), 80 MHz, Flash, 4M (3M SPIFFS), v2 Lower Memory, Disabled, None, Only Sketch, 115200"

In file included from /Users/i74790/Documents/Arduino/ESP8266_WS_V2.0_iT4IT.CLUB/ESP8266_WS_V2.0_iT4IT.CLUB.ino:24:0:
sketch/config.h: In member function 'bool config::read()':
config.h:151: error: 'DynamicJsonBuffer' was not declared in this scope
       DynamicJsonBuffer jsonBuffer;
       ^
config.h:151: error: expected ';' before 'jsonBuffer'
       DynamicJsonBuffer jsonBuffer;
                         ^
config.h:152: error: 'jsonBuffer' was not declared in this scope
       JsonObject &json = jsonBuffer.parseObject(configFile);
                          ^
config.h:154: error: 'class ArduinoJson650_0_0::JsonObject' has no member named 'success'
       if (json.success()) {
                ^
sketch/config.h: In member function 'bool config::write()':
config.h:171: error: 'DynamicJsonBuffer' was not declared in this scope
       DynamicJsonBuffer jsonBuffer;
       ^
config.h:171: error: expected ';' before 'jsonBuffer'
       DynamicJsonBuffer jsonBuffer;
                         ^
config.h:172: error: 'jsonBuffer' was not declared in this scope
       JsonObject& json = jsonBuffer.createObject();
                          ^
config.h:178: error: 'class ArduinoJson650_0_0::JsonObject' has no member named 'printTo'
       if (json.printTo(configFile)) {
                ^
sketch/config.h: In member function 'bool config::write(String)':
config.h:189: error: 'DynamicJsonBuffer' was not declared in this scope
     DynamicJsonBuffer jsonBuffer;
     ^
config.h:189: error: expected ';' before 'jsonBuffer'
     DynamicJsonBuffer jsonBuffer;
                       ^
config.h:190: error: 'jsonBuffer' was not declared in this scope
     JsonObject& json = jsonBuffer.parseObject(apiSave);
                        ^
config.h:191: error: 'class ArduinoJson650_0_0::JsonObject' has no member named 'success'
     if (json.success()) {
              ^
exit status 1
'DynamicJsonBuffer' was not declared in this scope

2 второй вопрос по плате ATmega 2560 с 32Мб - там встроен ESP8266, всё равно надо внешний подключать?

ATmega 2560 с 32Мб больше нет в наличии, может использовать обычную Мегу с SD-картой?

На Алиэкспрессе некоторые продавцы пишут, что это 32 Мбита=4Мбайта, что , скорее всего так и есть, тем более, что в даташите указано на максимальный теоретический объём памяти в 16Мбайт. Может, к ESP8266 прикрутить SD-карту или, если общаться с датчиками будет Мега, на долю ESP останется свой сервер и передача данных на удаленный сервер. В таком случае может и 3МБ хватит?

продавец в России подтвердил 32 Мегабита

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
17.10.2018 в 10:54, Devilisimo сказал:

Все супер, заработало.

Еще нужна небольшая подсказка. Хочу сделать универсальную подписку:


            switch2 = "switch/" + conf.param("mqtt_path") + "sw2";
            mqttAPI.subscribe(switch2);

Ругается: no matching function for call to 'PubSubClient::subscribe(String&)'

Какого типа должна быть переменная?

Вы всегда можете изучить возможности библиотеки, никакой магии там нет, просто откройте в Arduino IDE любой пример идущий в комплекте с библиотекой. Далее в меню "Скетч" выберите пункт "Показать папку скетча" и в открывшемся каталоге достаточно перейти в корень библиотеки, обычно не более двух переходов на каталог выше. Там Вы увидите список файлов и каталогов среди которых будет каталог src с исходными кодами библиотеки. Нас интересуют файлы заголовков, в данном случае файл PubSubClient.h

Открываем его любым удобным текстовым редактором, желательно с поддержкой перевода строки по ASCII 0x0A (в народе просто '\n'). И нам станет доступно описание всех методов интересующего класса.

image.png

Теперь можно изучить методы, а при необходимости посмотреть их реализацию в файле с расширением .ccp

В нашем случаем метод subscribe принимает в качестве первого аргумента указатель на переменную с типом const char

17.10.2018 в 14:22, Devilisimo сказал:

Хотел спросить у автора - так понимаю реализована функция, что если нет вай-фая, то переходит в режим точки доступа. Соответственно все данные по старому подключению стираются? Если дома отключился свет или еще что-то. Если мы хотим только через одно подключение делать все, то надо жестко его будет прописать?

В программе реализовано сохранение настроек в список которых входят данные для подключения к домашней базовой станции. Далее, если связь будет разорвана, то программа переведет контроллер из режима клиента (STA) в режим самостоятельной точки доступа (AP) и через небольшие интервалы времени будет производить сканирование эфира в поиске домашней базовой станции. Во время поиска все клиенты, подключенные к точке доступа контроллера, будут отключены, это связано с переводом контроллера в режим STA при сканировании. Если домашняя точка доступа будет найдена, то контроллер попытается к ней подключиться.

Также при удачном подключении контроллер запомнит MAC адрес домашней точки доступа. В последствии он будет использован для сравнения MAC адресов у скрытых точке доступа в поиске "дома". Этот механизм нуждается в доработке из-за некоторых специфических ситуаций, но вполне работоспособен и на данный момент.

17.10.2018 в 23:44, post125 сказал:

с сегодняшнего дня решил начать пробовать программировать ESP, взял Вашу стандартную прошивку, подсоединил поддержку ESP в Arduino IDE, выделил 3Мб под SPIFFS. залил "ESP8266 Sketch Data Upload". а воот надо ли нажимать кнопку обычной загрузки скетча?

В первую очередь необходимо загрузить программу в микроконтроллер, делается этот как и всегда через зугрузку программы. После этого необходимо залить файлы web сервера на flash через "ESP8266 Sketch Data Upload". При этом консоль (Serial монитор) должна быть закрыта т.к занимает порт.

17.10.2018 в 23:44, post125 сказал:

у меня выдает ошибку - с этим разобрался, а вот теперь пишет другие ошибки:

Ваша ошибка связана с использованием одной из Beta версий библиотеки ArduinoJson. Возьмите любую версию из 5.13.x, это упоминалось в инструкции. Также хочу напомнить, что не каждая версия ядра ESP8266 для Arduino IDE подойдет нам, я использую 2.4.0, а вот в версии 2.4.1 есть баг с утечкой памяти который приведет к серьезным проблемам! Будьте очень внимательны и следуйте рекомендациям.

17.10.2018 в 23:44, post125 сказал:

2 второй вопрос по плате ATmega 2560 с 32Мб - там встроен ESP8266, всё равно надо внешний подключать?

Если Вы говорите о плате от RobotDyn с двумя контроллерами на борту, то её хватит для решения большинства задач и подключения кучи датчиков, дисплеев и т.д и т.п, тут только фантазия может стать ограничивающим фактором. Но я бы отказался от встроенного способа связи между ними через UART в пользу I2C или SPI.

17.10.2018 в 23:44, post125 сказал:

ATmega 2560 с 32Мб больше нет в наличии, может использовать обычную Мегу с SD-картой?

Значение 32 относится к ESP8266, а к ATmega2560 можно подключить все, что Вам нравится. Но я не сторонник SD карт из-за их низкой надежности и возможных проблемах при выходе карты из строя. Для ведения журналов есть множество других способов, один из которых, это сторонний скрипт на домашнем сервере в роли которого может выступить подготовленный домашний маршрутизатор.

17.10.2018 в 23:44, post125 сказал:

На Алиэкспрессе некоторые продавцы пишут, что это 32 Мбита=4Мбайта, что , скорее всего так и есть, тем более, что в даташите указано на максимальный теоретический объём памяти в 16Мбайт.

Оно так и есть на самом деле,  8bit = 1byte. Вопрос только в том, как эффективно использовать эту память чтобы быстро не сократить её срок службы.

17.10.2018 в 23:44, post125 сказал:

Может, к ESP8266 прикрутить SD-карту или, если общаться с датчиками будет Мега, на долю ESP останется свой сервер и передача данных на удаленный сервер. В таком случае может и 3МБ хватит?

Если у Вас есть удаленный сервер, то лучше возложить на него все задачи, связанные с сохранением полученных данных. Это максимально эффективно и правильно.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
23 часа назад, Devilisimo сказал:

Если кому-то интересно выкладываю свою версию, она пока сырая, но работает.

Добрый день! Попробовал залить Вашу версию, не может найти библиотеку U8g2lib, Откуда закачать?

2 часа назад, Kitsum сказал:

ошибка связана с использованием одной из Beta версий библиотеки ArduinoJson

Благодарю за ответы, прошу прощения за невнимательность - на версии вообще не обратил внимание. Все залилось, точка доступа появилась, приступаю к изучению. Тёмный лес, конечно же для меня, но "дорогу осилит идущий". Спасибо!

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Всем доброе время суток. Допустил опечатку при указании версий ядра ESP8266 для Arduino IDE. Поправил свой пост.

Теперь еще раз и о главном - в ядре версии 2.4.1 имеется баг с утечкой памяти при работе с объектом WiFiClient. В версии 2.4.2 данный баг исправлен, по крайней мере об этом гласит ветка на GitHub. Обновитесь до 2.4.2 или откатывайтесь до 2.4.0 в зависимости от Ваших изменений в проекте.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
10/15/2018 в 01:33, Kitsum сказал:

@pasha413 Судя по документации это flash на 8Mb, но в интернете есть упоминания, что с P25Q80H много проблем. Вопрос досконально не изучал, но вот интересная тема на Хабре: https://habr.com/post/409911/

 

Столкнулся в процессе отладки с неустранимым исключением (28), esp постоянно крутился в бесконечном цикле, выдавая это исключение и дамп стека. Танцы с разными бубнами, найденными в нете, результатов не дали. В числе рекомендаций была замена м/с флэш.

Когда наконец дошли руки - приступил. Отпаял защитный экран с модуля, снял м/с флэш, оказалась winbond 25q32jvs. Установил в программатор, читаю, стираю, записываю - все хорошо. Чешу репу... снова ее запаиваю, накатываю прошивку- не работает, но исключений нет. В какой-то момент показалось, что есть непропай ножек, пропаиваю и после этого снова покатили исключения, только уже (29). Плюю на все, выпаиваю и ставлю новую winbond 25q32bvs.

И о чудо ! После прошивки все работает, даже правильно показываются события во вкладке 'система' , а то там вечно писались исключения.

Т.е. получается, что замена м/с решила проблему, посмотрим на долго ли.

Новые м/с заказывал на Али, по ценнику выше среднего.

IMG_20181021_142856.jpg

Изменено пользователем EVG
upload image
  • Thanks 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

post125

библиотека: https://github.com/olikraus/U8g2_Arduino/archive/master.zip

Kitsum 

Я разобрался c данной проблемой, ниже выкладываю код, вероятно он не идеален, но работает. Пока сделал функции для управления реле.

В мыслях есть создание функций для управление нагрузкой через ШИМ на основе мосфетов. Какие модули можете посоветовать?

https://ru.aliexpress.com/item/Milight-LS4-0-1-10/32852443849.html?spm=a2g0s.13010208.99999999.259.101e3c00l1IpCP

https://ru.aliexpress.com/item/Mosfet-MOS/32878055897.html?spm=a2g0s.13010208.99999999.271.101e3c00l1IpCP

https://ru.aliexpress.com/item/High-Current-MOSFET-Switch-Module-DC-Fan-Motor-LED-Strip-Driver-Steples/32661148624.html?spm=a2g0s.13010208.99999999.265.101e3c00l1IpCP

// Универсальная подписка на выключатель в формате "switch/'имя нашего esp'/switch'№gpio'"
void subgpio(byte gpio){
    String strpath;
    strpath += "switch/";
    strpath += conf.param("mqtt_path");
    strpath += "/switch";
    strpath += String(gpio);   
    mqttAPI.subscribe(strpath.c_str());
}
//Проверка выключателей по подписке
boolean callswitch(String topic, String msg, byte gpio){
    boolean sw; 
    if (conf.param("mqtt_path").length())
    {
    String strpath;
    strpath += "switch/";
    strpath += conf.param("mqtt_path");
    strpath += "/switch";
    strpath += String(gpio);   
    mqttPublish("switch/on",strpath); 
    String sending;
    if ((topic == strpath) and ((msg == "on") or (msg == "ON") or (msg == "On")))
    {     sw = true;   
          sending += "sw_status_";
          sending += String(gpio);
          sending += "_send_GPIO";
          mqttPublish("switch/on",sending);        
      }
    else if ((topic == strpath) and ((msg == "off") or (msg == "OFF")) and sw == true) sw = false; 
    }
    return sw;
}

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
01.03.2018 в 10:49, pasha413 сказал:

Здравствуйте. Благодарю за проделанную работу!

Возник вопрос, загрузил проект в wemos d1mini, что со шрифтами?

 

Добрый день. Заказал на али плату Wavgat D1 mini, так понимаю это аналог Wemos D1 mini?

Загрузить ничего на него не получается и файлы в флеш прошить тоже. Может подскажете что-то, раз имели практику работы.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

@Devilisimo доброе время суток.

Если мы говорим о переменном токе, то можно собрать собственную схему на симисторе и подобрать его по требуемым характеристикам. Вот вполне полное видео по данной теме.

А вот для постоянного тока.

В любом случае, все упирается в характеристики элементной базы и нагрузку.

PS: соблюдайте предельную осторожность при работе с абсолютно любой нагрузкой, даже не большие токи могут привести к летальному исходу!

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
17 часов назад, Devilisimo сказал:

Добрый день. Заказал на али плату Wavgat D1 mini, так понимаю это аналог Wemos D1 mini?

Загрузить ничего на него не получается и файлы в флеш прошить тоже. Может подскажете что-то, раз имели практику работы.

Kitsum  подскажите пожалуйста. Прошил модуль nodemcu_integer_0.9.5_20150318 через nodemcu firmware programmer.

Скетч заливается успешно и даже как-то работает. Но залить файлы в память не удается, ошибка SPIFFS Create Failed! 

В чем может быть причина? Прошивки AT20SDK93 и подобные не работают, сыпятся ошибки.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Здравствуйте!

В режиме отладки и настройки метеостанции всё работало замечательно, но после установки "домика" на улице наблюдаю уже несколько месяцев такую картину:

Может кто нибудь сталкивался с этим, в чём причина и методы  "лечения".

Спасибо

Снимок.JPG

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Доброе время суток.

23.10.2018 в 10:10, Devilisimo сказал:

Kitsum  подскажите пожалуйста. Прошил модуль nodemcu_integer_0.9.5_20150318 через nodemcu firmware programmer.

Скетч заливается успешно и даже как-то работает. Но залить файлы в память не удается, ошибка SPIFFS Create Failed! 

В чем может быть причина? Прошивки AT20SDK93 и подобные не работают, сыпятся ошибки.

К сожалению, мне довольно сложно ответить на данный вопрос т.к я не сторонник загрузки данных через данную программу. Знаю, что необходимо указать несколько скомпилированных файлов с указанием соответствующих им адресов. Адреса могут меняться в зависимости от используемой микросхемы флеш памяти. Обратитесь к официальной документации или используйте Arduino IDE для загрузки данных. В пользу среды разработки в качестве программы загрузки могу сказать, что она дает простой способ произвести тонкие настройки для ESP8266 благодаря которым, начиная с версии ядра 2.4.2 можно разогнать скорость работы с сетевым трафиком в несколько раз. У меня получилось добиться загрузки самого тяжелого файла проекта (css файл с изображениями облаков) за 1.4 секунды внутри локальной сети.

18 часов назад, PulFred сказал:

Может кто нибудь сталкивался с этим, в чём причина и методы  "лечения".

Полагаю, что график взять с "Народного мониторинга". Эти пробелы наблюдаются на собственном графике метеостанции?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
6 часов назад, Kitsum сказал:

Полагаю, что график взять с "Народного мониторинга"

Совершенно верно - с него. А собственный график строится, но от какого-то произвольного времени. При этом не всегда удаётся сразу войти в вебинтерфейс метеостанции - браузер сообщает, что страница недоступна.  

Снимок1a.JPG

Снимок1.JPG

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

@PulFred график метеостанции, это опорная точка. Нам наверняка известно, что график начинает строиться через 10 минут после старта микроконтроллера. Я могу сделать вывод, что примерно в 9 часов вечера микроконтроллер начал выполнять свою работу. Также может быть так, что у контроллера сработал сторожевой таймер, что привело к перезапуску и сбросу всех данных в памяти, в том числе и графика.

Куда стоит обратить внимание:

  1. В первую очередь проверьте качество питания.
  2. Все модификации должны корректно отрабатывать в любом режиме работы микроконтроллера AP/STA, особенно если они контактируют с внешним миром.
  3. Убедитесь, что в коде нет утечки памяти. Эта проблема проявляется в версии 2.4.1 ядра ESP8266. Также эта проблема может быть вызвана модификациями.
  4. Если к метеостанции есть доступ из интернета, его необходимо закрыть т.к web сервер микроконтроллера не предназначен для массового наплыва клиентов, а ходить на веб сервер буду 100%. Среди клиентов сразу появятся различные сканеры и т.п программы.

Зайдите в Настройки, раздел Система. Убедитесь, что объем свободной памяти сразу после запуска микроконтроллера примерно совпадает с объемом через, допустим, час или два после запуска. Заодно проверьте версию ядра (2.4.0 или 2.4.2).

image.png 

В самом конце этого раздела проверьте время беспрерывной работы. В моем случае таймер указывает на 9 дней и 4 часа, но на самом деле, через каждые 40 с лишним дней происходит переполнение millis и таймер начинает отсчет с нуля. Но для понимания происходящего этого вполне достаточно.

image.png

По своему варианту метеостанции (опубликованный исходник с выбранными датчиками + один программный датчик) с отправкой данных на три разные площадки в интернете, я делаю вывод, что в течении последних 9-и дней нет проблем с питанием и переполнением памяти.

image.png image.png

Постарайтесь провести аналогичный анализ, только в Вашем случае перезапустите микроконтроллер и запомните сколько оперативной памяти было свободно. Понаблюдайте в течении дня за памятью и внутренним таймером. На крайний случай воспользуйтесь, для сравнения, стоковой прошивкой с модификациями кода микроконтроллера и web интерфейса только под свои датчики.

Буду надеяться, что проблему получится выявить сразу.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
33 минуты назад, Kitsum сказал:

Куда стоит обратить внимание:

Здравствуйте

Спасибо за развёрнутые рекомендации. По наличию времени буду пытаться найти причину.

О результатах отпишусь 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Здравствуйте. снова добрался до ESP-01, решил подключить BME280 к выводам 0 и 2, показаний нет, при сканировании датчик то появляется то пропадает. так же происходит и с другими датчиками i2c. в чем может быть проблема? DHT22 работал нормально

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Скрытый текст

// Универсальная подписка на выключатель в формате "switch/'имя нашего esp'/switch'№gpio'"
void subgpio(byte gpio){
    String strpath;
    strpath += "switch/";
    strpath += conf.param("mqtt_path");
    strpath += "/switch";
    strpath += String(gpio);   
    mqttAPI.subscribe(strpath.c_str());
}
//Проверка выключателей по подписке
boolean callswitch(String topic, String msg, byte gpio){
    boolean sw; 
    if (conf.param("mqtt_path").length())
    {
    //sw = true;  
    String strpath;
    strpath += "switch/";
    strpath += conf.param("mqtt_path");
    strpath += "/switch";
    strpath += String(gpio);   
    //mqttPublish("switch/on",strpath); 
    String sending;
    if ((topic == strpath) and ((msg == "on") or (msg == "ON") or (msg == "On")))
    {     sw = true;   
          sending += "sw_status_";
          sending += String(gpio);
          sending += "_send_GPIO";
          mqttPublish("switch/on",sending);        
      }
    else if ((topic == strpath) and ((msg == "off") or (msg == "OFF"))) 
    {     sw = false; 
          sending += "sw_status_";
          sending += String(gpio);
          sending += "_send_OFF";
          mqttPublish("switch/on",sending);    
      }
     
    }
    return sw;
}

void callback(char* topic, byte* payload, unsigned int length) {
    String msg;
    for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
    String top;
    top += topic;
    //Опрашиваем кнопки mqtt управления
    sw_on_0 = callswitch(top, msg, 0);   
    sw_on_12 = callswitch(top, msg, 12);
    sw_on_13 = callswitch(top, msg, 13);       
    sw_on_14 = callswitch(top, msg, 14);
    sw_on_15 = callswitch(top, msg, 15);
                   
    #ifdef console
       console.printf("send=", msgtopic.c_str());
    #endif 
}

void setupMQTT() {
  if (conf.param("mqtt_server").length()) {
    /* Указываем сервер и порт для подключения. Сервер можно указать через WEB интерфейс, порт измените на свой. */
    mqttServer = conf.param("mqtt_server");
    mqttAPI.setServer(mqttServer.c_str(), mqttPort);
    mqttPath = conf.add("mqtt_path");
    /* Устанавливаем обработчик  mqttPath.c_str() */
    mqttAPI.setCallback(callback);  
  
    /* Задача для восстановления соединения */
    cron.add(cron::time_1m, [&](){
      if (!mqttAPI.connected() and wifi.transferDataPossible()) {
        if(mqttAPI.connect(WiFi.hostname().c_str(),
          (conf.param("mqtt_login").length() ? conf.param("mqtt_login").c_str() : 0),
          (conf.param("mqtt_pass").length() ? conf.param("mqtt_pass").c_str() : 0)
        )) {
          /* Список топиков на которые необходимо оформить подписку */

            subgpio(12);
            subgpio(13);
            subgpio(14);
            subgpio(15);

            connect_OK = true;
        }
        #ifdef console
          console.printf("Connecting to MQTT server\nanswer: %s\n", mqttCodeStr(mqttAPI.state()).c_str());
        #endif
      }
    }, false);        
  }
  /* end if */
}

 

@Kitsum Прошу помощь советом. Данный код для опроса управления GPIO. При планомерной работе все хорошо. Если включить подряд несколько выходов, подать команду. Происходит срабатывание только последнего выхода в списке команд. Может чем-то поможете.

Скрытый текст

/*
   Управление нагрузкой :
    GPIO  */
void gpio_sw(){    
     /* gpio=0/12/13/14/15  |  D=3/6/7/5/8 управление | D 3/8 нежелательно использовать */
    //pinMode(0, OUTPUT); digitalWrite(0, !gpio_enable);  //D3
    
      pinMode(12, OUTPUT); digitalWrite(12, !gpio_enable); //D6
      pinMode(13, OUTPUT); digitalWrite(13, !gpio_enable);  //D7
    
    pinMode(14, OUTPUT); digitalWrite(14, !gpio_enable); //D5
    pinMode(15, OUTPUT); digitalWrite(15, !gpio_enable); //D8
    /* Добавление задачи в планировщик */
    cron.add(cron::time_5s, [&](){
      if (connect_OK == true)
      { 
        //gpio 0 D3 нежелательно
        if (sw_on_0 == true and digitalRead(0) == !gpio_enable) 
        { digitalWrite(0, gpio_enable); mqttPublish("switch/on","gpio_0_is_ON"); }         
        else if (sw_on_0 == false and digitalRead(0) == gpio_enable) digitalWrite(0, !gpio_enable);
        //gpio 12 D6 нельзя использовать есть подключен MH-Z19
        if (sw_on_12 == true and digitalRead(12) == !gpio_enable) 
        { digitalWrite(12, gpio_enable); mqttPublish("switch/on","gpio_12_is_ON"); }         
        else if (sw_on_12 == false and digitalRead(12) == gpio_enable) digitalWrite(12, !gpio_enable);
        //gpio 13 D7 нельзя использовать есть подключен MH-Z19
        if (sw_on_13 == true and digitalRead(13) == !gpio_enable) 
        { digitalWrite(13, gpio_enable); mqttPublish("switch/on","gpio_13_is_ON"); }         
        else if (sw_on_13 == false and digitalRead(13) == gpio_enable) digitalWrite(13, !gpio_enable);
        //gpio 14 D5
        if (sw_on_14 == true and digitalRead(14) == !gpio_enable) 
        { digitalWrite(14, gpio_enable); mqttPublish("switch/on","gpio_14_is_ON"); }         
        else if (sw_on_14 == false and digitalRead(14) == gpio_enable) digitalWrite(14, !gpio_enable);
        //gpio 15 D8 нежелательно
        if (sw_on_15 == true and digitalRead(15) == !gpio_enable) 
        { digitalWrite(15, gpio_enable); mqttPublish("switch/on","gpio_15_is_ON"); }         
        else if (sw_on_15 == false and digitalRead(15) == gpio_enable) digitalWrite(15, !gpio_enable);

      }
    });
}

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Пожалуйста, войдите для комментирования

Вы сможете оставить комментарий после входа



Войти сейчас

  • Похожие публикации

    • Автор: Kitsum
      Хотите помочь проекту или спонсировать новый?
      Yandex.Money PayPal.me Тема проекта
      Arduino IDE + Project + Libraries + tools: https://yadi.sk/d/jseefFB50NMhAg
    • Автор: Kitsum
      Просмотреть файл [esp8266] Библиотека CMD, реализует настройку микроконтроллера и управление вашей программой через терминал.
      Основная задача библиотеки, это прием пользовательских команд через UART интерфейс, их обработка и выполнение пользовательского кода, связанного с той или иной командой.
      Данная библиотека позволяет реализовать:
      Управление микроконтроллером Любую настройку, будь то WiFi, другие библиотеки или часть Вашей программы Вызывать Ваши задачи (функции) из терминала по команде и передавать им требуемые параметры Использовать контроллер в качестве шлюза между датчиками и программами на PC Внимание: любая команда, передаваемая в терминал обязана заканчиваться символом перевода строки "\n".
      Подключение библиотеки
      #include <cmd.h> Инициализация объекта, к которому мы будем обращаться для добавления команд. В качестве параметра объекту необходимо передать указатель на объект Serial или любой другой схожий по типу интерфейс.
      cmd command(&Serial); В функции Setup описываем какие команды требуется обрабатывать. Например, по команде "test" вызывать пользовательскую функцию с именем "myFunctionName". Имя пользовательской функции может быть абсолютно любым.
      void Setup() { Serial.begin(115200); command.add("test", myFunctionName); } Пользовательская функция будет вызываться каждый раз, когда по интерфейсу Serial поступит команда "test". Если команда будет передана с параметрами, то эти параметры будут переданы в качестве аргументов пользовательской функции.
      В функции loop должна находится команда вызова обработчика.
      void loop() { command.handleEvents(); } Пользовательская функция обязана соответствовать ряду требований:
      Не возвращать никакого результата (быть объявленной с типом void) Принимать в качестве первого аргумента переменную с типом byte в которой будет храниться число равное количеству переданных параметров Принимать в качестве второго параметра переменную с типом char** в которой будет храниться указатель на массив со всеми указателями (char*) на переданные параметры void myFunctionName(byte argc, char** argv) { /* ... */ } Функция всегда должна иметь такой вид, даже если не подразумевается, что ей будут передаваться какие-либо параметры.
      Чтобы перебрать все переданные параметры и вывести их в консоль, можно воспользоваться следующим примером
      void myFunctionName(byte argc, char** argv) { if (0 < argc) { for (uint8_t i = 0; i < argc; i++) { Serial.printf("%i. %s\n", i, argv[i]); } } } Пример вызова пользовательской функции без параметров и с ними
      # test No parameter was passed # test p1 p2 p3 p4 p5 0. p1 1. p2 2. p3 3. p4 4. p5 Помните, что параметры представлены в виде указателей и работать с ними нужно как с обычными переменными не получится т.к указатель содержит не значение переменной (переданный параметр), а указатель на ту область памяти микроконтроллера в которой это значение находится.
      Чтобы сравнить два значения, например, параметр под индексом 0 (идет первым в списке) с каким-либо значением в программе, воспользуйтесь функцией strcmp, которая возвращает целочисленное значение, указывающее на лексическое расхождение строк. Если строки равны, то возвращаемое значение равно 0.
      if (!strcmp(argv[0], "wifi")) { Serial.println(F("Первый аргумент WiFi")); } else { Serial.println(F("Первый аргумент НЕ WiFi!!!")); } Для копирования значения указателя в другую переменную с типом char можно воспользоваться функцией strcpy
      char myVar[20]; strcpy(myVar, argv[0]); if (myVar == "123456") { Serial.prinln(F("ok")); } Также можно обернуть указатель объектом String и получить весь функционал этого объекта, который будет содержать значение параметра
      String param1(argv[0]); // String param1 = argv[0]; Serial.printf("argv[0] length: %i\n", param1.length()); Serial.printf("argv[0] is integer?: %s\n", param1.toInt() ? "YES" : "NO"); if (param1 == "qwerty") { Serial.println(F("Hello QWERTY!")); } С библиотекой идут несколько примеров, в том числе и пример конфигурации WiFi в режиме STA.
      Автор Kitsum Добавлен 05.12.2018 Категория Библиотеки  
    • Автор: Kitsum
      Просмотреть файл [esp8266] Библиотека smartBlink, реализует умное управление штатным светодиодом, что позволяет добавить индикацию состояния вашей программы или микроконтроллера.
      Основная задача библиотеки, это добавление индикации состояния Вашей программы или микроконтроллера. Отображение состояния производится посредством светодиода. Что самое важное, работа библиотеки через прерывание, это позволяет ей поддерживать индикацию даже в то время, когда выполняется длительный код основной программы. Например, Вы можете использовать её для отображения в каком режиме сейчас работает WiFi микроконтроллера, STA или AP и т.д. Или ход выполнения какой-либо операции, например, передача данных на внешний сервер.
      Подключение библиотеки
      #include <smartBlink.h> Чтобы инициализировать управление светодиодом необходимо создать объект, через который мы буем задавать режимы работы индикации.
      smartBlink::smartBlink(byte gpio, bool on = LOW); Объекту необходимо передать два параметра, первый это номер порта, на котором находится светодиод, а второй это уровень логического сигнала, который заставит светодиод работать. Сигнал может быть низким (LOW) или высоким (HIGH), это зависит от схемотехники подключения светодиода.
      Например, штатный светодиод модуля ESP12, использующий GPIO2 (порт 2) можно объявить следующим образом.
      #define led2_pin 2 #define led2_on_signal LOW smartBlink led2(led2_pin, led2_on_signal); Теперь можно в основной программе использовать метод устанавливающий какой режим индикации использовать.
      smartBlink::setMode(mode_t mode); Например, зададим режим светодиода led2 в котором светодиод будет давать одну короткую вспышку раз в секунду.
      led2.setMode(smartBlink::mode_flash1); Режимов работы может быть несколько.
      led2.setMode(smartBlink::mode_off); led2.setMode(smartBlink::mode_flash1); led2.setMode(smartBlink::mode_flash2); led2.setMode(smartBlink::mode_flash3); led2.setMode(smartBlink::mode_flash4); led2.setMode(smartBlink::mode_burn); led2.setMode(smartBlink::mode_inhalf); Чтобы вернуть предыдущий режим индикации для ранее объявленного светодиода led2 используйте следующий метод
      led2.previous(); Благодаря работе библиотеки через прерывания по таймеру, индикация будет работать даже в тех случаях, когда выполняется долгий код.
      С библиотекой идут несколько примеров.
      Автор Kitsum Добавлен 10.12.2018 Категория Библиотеки  
    • Автор: Kitsum
      Основная задача библиотеки, это добавление индикации состояния Вашей программы или микроконтроллера. Отображение состояния производится посредством светодиода. Что самое важное, работа библиотеки через прерывание, это позволяет ей поддерживать индикацию даже в то время, когда выполняется длительный код основной программы. Например, Вы можете использовать её для отображения в каком режиме сейчас работает WiFi микроконтроллера, STA или AP и т.д. Или ход выполнения какой-либо операции, например, передача данных на внешний сервер.
      Подключение библиотеки
      #include <smartBlink.h> Чтобы инициализировать управление светодиодом необходимо создать объект, через который мы буем задавать режимы работы индикации.
      smartBlink::smartBlink(byte gpio, bool on = LOW); Объекту необходимо передать два параметра, первый это номер порта, на котором находится светодиод, а второй это уровень логического сигнала, который заставит светодиод работать. Сигнал может быть низким (LOW) или высоким (HIGH), это зависит от схемотехники подключения светодиода.
      Например, штатный светодиод модуля ESP12, использующий GPIO2 (порт 2) можно объявить следующим образом.
      #define led2_pin 2 #define led2_on_signal LOW smartBlink led2(led2_pin, led2_on_signal); Теперь можно в основной программе использовать метод устанавливающий какой режим индикации использовать.
      smartBlink::setMode(mode_t mode); Например, зададим режим светодиода led2 в котором светодиод будет давать одну короткую вспышку раз в секунду.
      led2.setMode(smartBlink::mode_flash1); Режимов работы может быть несколько.
      led2.setMode(smartBlink::mode_off); led2.setMode(smartBlink::mode_flash1); led2.setMode(smartBlink::mode_flash2); led2.setMode(smartBlink::mode_flash3); led2.setMode(smartBlink::mode_flash4); led2.setMode(smartBlink::mode_burn); led2.setMode(smartBlink::mode_inhalf); Чтобы вернуть предыдущий режим индикации для ранее объявленного светодиода led2 используйте следующий метод
      led2.previous(); Благодаря работе библиотеки через прерывания по таймеру, индикация будет работать даже в тех случаях, когда выполняется долгий код.
      С библиотекой идут несколько примеров.
  • Сейчас на странице   0 пользователей

    Нет пользователей, просматривающих эту страницу.

×
×
  • Создать...