Дачная метеостанция

step962
Offline
Зарегистрирован: 23.05.2011

Весной этого года у меня на даче появилась метеостанция, следящая за температурой, давлением и влажностью воздуха на участке. В ее состав входит модуль Blue Pill (отладочная плата с контроллером STM32f103C8T6) с подключенными к нему модулями датчиков BMP180 (температура и давление), DHT22 (температура и относительная влажность воздуха) и термометром DS18B20, а также модулем SD-карточки (журнал измерений), далее - модуль NodeMCU, работающий в качестве моста между Blue Pill и USB-модемом, работающим в режиме WiFi-роутера. Такая связка позволила не только вести запись метеоданных, но и следить за их изменением из дома.

Первая версия программы создавалась в некоторой спешке - хотелось успеть до начала вегетативного сезона с его угрозами заморозков. Успеть удалось, но за счет использования тех компонентов, что были под рукой (BMP180+DHT22). Устройство без проблем отработало весну/лето/осень, продолжает удаленно следить за погодой на даче и сейчас, но за прошедшее время накопились некоторые идеи расширения функциональности и пришли модули BME280, позволяющие избавиться от датчика DHT22.

Это предыстория...

История же состоит в глубоком редактировании - практически переписывании заново - старой версии.

Шаг за шагом, без весенней спешки, будут реализованы уже проверенные функции и добавлены новые. Приведенный ниже скетч - это первый этап. Данные считываются с датчика BME280 и двух термометров DS18B20 и через COM-порт выводятся на терминал. Кроме того, текущие значения доступны для просмотра со смартфона или другого Android-устройства. 

/* Метеостанция - стартовый вариант: "Показометр" на базе отладочной платы Blue Pill
 *  (STM32F103C8T6) с подключенными к ней модулями bme280 (давление, температура, влажность)
 *  и двумя термодатчиками ds18b20
 */

// работа с bme280
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

// работа с ds18b20
//#include <OneWire.h>
#include <OneWireSTM.h>
#define DS_DATA_PIN0 PB8
#define DS_DATA_PIN1 PB9

// модули поддержки работы с MoDyz == (C\Users\SAP\Documents\Arduino\libraries\MD_Scout) ==
#include <mdwriter.h>
#include <mdsender_bt.h>

#define LOG_SLICE 10
#define _WITH_SER1

// независимое питание часов (3V) - батарейка CR2032, подключаемая к выводам Gnd и VB
// Serial (USB) - интерфейс с компьютером, при инициализации скорость порта не указывается
//                (USB!), терминальная программа может работать на любой скорости
// Serial1 (PA9-TX,PA10-RX) - интерфейс с модулем Bluetooth на скорости последовательного
//                порта по умолчанию (9600 бод). Модуль spp-c без проблем коммуницирует на
//                такой скорости
// set up variables using the SD utility library functions:

int inByte;
long nextMillis;
long Pascals, mmHg;
float bme_t, bme_h, ds_t[2], Tp;
float t_min[3], t_max[3], p_min, p_max, h_min, h_max;

Adafruit_BME280 bme; // use I2C interface

//byte ds_data[12], addr[8];
OneWire  ds0(DS_DATA_PIN0);
OneWire  ds1(DS_DATA_PIN1);
MD_Sender_BT mdScout;
String sResponse, sCommand1;

void setup() {
  delay(2000); // задержка для реинициализации USB-порта
  // initialize the digital pin as an output. // ???
  pinMode(PC13, OUTPUT);                      // ???
  Serial.begin();  // USB does not require BAUD
  Serial1.begin(9600);  // Канал для связи с модулем Bluetooth
  Serial.println( "+-----------------------------------+");
  Serial.println( "| ========= STM32_Meteo_0 ========= |");
  Serial.println( "+-----------------------------------+");
  Serial1.println("+-----------------------------------+");
  Serial1.println("| ========= STM32_Meteo_0 ========= |");
  Serial1.println("+-----------------------------------+");

  for(int i=0;i<3;i++) {t_min[i] = 1000; t_max[i] = -1000; }
  p_min = 9999; p_max = -1000;
  h_min = 1000; h_max = -1000;
  
  nextMillis = millis();  // первое считывание информации с датчиков - сразу после старта
  Serial.println("Initialize BME280...");
  if (!bme.begin()) {
    Serial.println(F("Could not find a valid BME280 sensor, check wiring!"));
    while (1) delay(10);
  }
} // of setup

void loop() {
  char c, i;

  readHwSerial(Serial1,sCommand1);

//  if(millis()>=nextMillis) {
  if((long)(nextMillis-millis())<=0) {
    nextMillis = nextMillis + 10000;  // опрос датчиков  - раз в 10 секунд
    getData();            // считывание показаний с датчиков
    sendDataToTerminal(); // вывод информации в терминал
  }
} // of loop

void readTemp(OneWire ds,float &t) {
  byte present = 0;
  byte i;
  byte addr[8];
  byte data[12];
  float celsius;

  ds.reset_search();
  
  if(!ds.search(addr)) {
    Serial.println("No more addresses.");
    Serial.println();
    ds.reset_search();
    delay(250);
    return;
  }
  
  if(OneWire::crc8(addr, 7) != addr[7]) {
    Serial.println("CRC is not valid!!!");
    delay(250);
    return;
  }
  ds.reset();
  ds.select(addr);
  ds.write(0x44, 1);
  delay(1000);
  present = ds.reset();
  ds.select(addr);
  ds.write(0xBE);

  for(i=0;i<9;i++) data[i] = ds.read();

  int16_t raw = (data[1]<<8) | data[0];
  byte cfg = (data[4] & 0x60);
  if(cfg==0x00) raw = raw & ~7;
  else if(cfg==0x20) raw = raw & ~3;
  else if(cfg==0x40) raw = raw & ~1;
  celsius = (float)raw/16.0;
  t = celsius;
} // of readTemp

float calculateDevPoint(float t,float h) {
  float a=17.27,b=237.7,v;
  v = a*t/(b+t)+log(h/100);
  Tp=b*v/(a-v);
  return Tp;
} // of calculateDevPoint

void calcMinMax(float val, float *min_val, float *max_val) {
  if(*min_val>val) *min_val = val;
  if(*max_val<val) *max_val = val;
} // of calcMinMax

void getData() {
// чтение температуры, давления и влажности с модуля bme280
    bme_t = bme.readTemperature();
    Pascals = bme.readPressure();///100.0;
    bme_h = bme.readHumidity();
    mmHg = round(Pascals / 133.3223684 * 10);

    calcMinMax(bme_t,&t_min[0],&t_max[0]);
    calcMinMax(bme_h,&h_min,&h_max);
    calcMinMax(mmHg,&p_min,&p_max);

// чтение температуры с датчиков ds18b20
    readTemp(ds0,ds_t[0]);
    readTemp(ds1,ds_t[1]);
    calcMinMax(ds_t[0],&t_min[1],&t_max[1]);
    calcMinMax(ds_t[1],&t_min[2],&t_max[2]);
  calculateDevPoint(bme_t,bme_h);
} // of getData

void sendDataToTerminal() {
  Serial.print("==== mTime: "); Serial.println(millis());
  Serial.print(F("Temperature.......: ")); Serial.print(bme_t, 1);      Serial.print(F(" +-1.0°C, "));
  Serial.print(F("Pressure..........: ")); Serial.print(Pascals);       Serial.print(F(" +-100Pa"));
  Serial.print(F(" ("));                   Serial.print(mmHg/10.0, 1);  Serial.println(F(" mmHg)"));
  Serial.print(p_min/10.0,1);              Serial.print("  ");          Serial.println(p_max/10.0,1);
  Serial.print(F("Humidity.......: "));    Serial.print(bme_h, 0);      Serial.println(F(" +-2%"));
  Serial.print(h_min,0);                   Serial.print("  ");          Serial.println(h_max,0);
  Serial.print(F("Temp in...........: ")); Serial.print(ds_t[0], 1);   Serial.println(F(" +-0.5°C"));
  Serial.print(F("Temp out..........: ")); Serial.print(ds_t[1], 1);   Serial.println(F(" +-0.5°C"));
  Serial.print(F("Dev.point.........: ")); Serial.print(Tp, 1);        Serial.println(F(" +-0.4°C"));
  Serial.flush();
} // of sendDataToTerminal

void readHwSerial(HardwareSerial S, String &sCmd) {
  char c;
    if (S.available()) {
    inByte = S.read();
    Serial.write(inByte);
// работа с MoDyz =======================
    c = inByte;
    if(c=='\r')       interpreteCommand(sCmd);
    else if(c=='\n')  interpreteCommand(sCmd);
    else sCmd = sCmd + c;
  }
} // of readHwSerial

void sendString() {
  for(int i=0;i<sResponse.length();i++)
    Serial1.write(sResponse.charAt(i));
  Serial1.write('\r'); Serial1.write('\n');
  sResponse = "";
} // of sendString

void addCmdString(String sCmd) {
  sResponse += sCmd;
  if(sResponse.length()>100) sendString();
} // of addCmdString

void showData() {
  // координаты оптимизированы для экрана 800x1280
  sResponse = "";
  
  addCmdString(mdScout.setDisplayColors(C_BLACK,C_LTGRAY));
  addCmdString(mdScout.setLineColors(C_LTGRAY,C_MAGENTA));
    addCmdString(mdScout.fillRectangleBG(5,5,-5,-5));
    addCmdString(mdScout.fillRectangleFG(8,8,-8,-8));
  addCmdString(mdScout.fillRectangleBG(150,80,680,440));
  addCmdString(mdScout.setTextColors(C_RED,C_LTGRAY));
  addCmdString(mdScout.setTextSize(40));

  addCmdString(mdScout.setTextColors(C_BLUE,C_LTGRAY));
  addCmdString(mdScout.outTextR(300,170,"BME280:"));
  addCmdString(mdScout.outTextL(320,170,String(bme_t,1) + " °C"));
  addCmdString(mdScout.outTextL(320,220,String(mmHg/10.0,1) + " mmHg"));

  addCmdString(mdScout.setTextColors(C_CYAN,C_LTGRAY));
  addCmdString(mdScout.outTextR(300,270,"BME280:"));
  addCmdString(mdScout.outTextL(320,270,String(bme_h,0) + " %"));
  
  addCmdString(mdScout.setTextColors(C_RED,C_LTGRAY));
  addCmdString(mdScout.outTextR(300,320,"ds18:"));
  addCmdString(mdScout.outTextL(320,320,String(ds_t[0],1) + " °C"));
  addCmdString(mdScout.outTextL(320,370,String(ds_t[1],1) + " °C"));
  addCmdString(mdScout.setTextColors(C_YELLOW,C_LTGRAY));
  addCmdString(mdScout.outTextR(300,420,"DevPt:"));
  addCmdString(mdScout.outTextL(320,420,String(Tp,1) + " °C"));

  sendString();
} // of showData

void interpreteCommand(String &sCmd) {
  if(sCmd.length()>0) {
    Serial.print("Interprete command: ["); Serial.print(sCmd); Serial.println("]");
    if(sCmd.equals("redrw"))    showData();     // на смартфоне была нажата кнопка "redrw"
    else if(sCmd.equals("drw")) showData();     // на смартфоне была нажата кнопка "drw"
    else if(sCmd.equals("fun1"));               
    // ...
    else if(sCmd.equals("fun6"));
    sCmd = "";
  }
} // of interpreteCommand

Чтобы обеспечить этот просмотр, на смартфоне необходимо установить приложение MoDyz, а в Arduino IDE добавить библиотеку MD_Scout. Как это сделать, будет сообщено в продолжении...

AndreyD
AndreyD аватар
Offline
Зарегистрирован: 07.10.2018

Я себе на улицу летом поставил BME280, пока полёт нормальный. А раньше ставил DHT22, а они через несколько месяцев начинали "залипать" по влажности к верхнему пределу, приходилось менять.

Igor_116
Offline
Зарегистрирован: 09.11.2017

step962

Добрый день.

а, можно схему увидеть ? и по подробней про NodeMCU

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

интересный выбор железа, а почему не ESP32 к примеру

step962
Offline
Зарегистрирован: 23.05.2011

AndreyD:

У меня в первом (запущенном весной) варианте модули BMP180 и DHT22 расположены под крышей бани (на мансарде), а метровый поводок термодатчика DS18B20 выведен наружу с северной стороны, где он постоянно находится в тени и не нагревается солнцем. В новом варианте связка BMP180+DHT22 будет заменена на BME280 и появится место под два термодатчика DS18B20. По опыту эксплуатации первого варианта на термометр BMP180/BME280 я бы не полагался - он стабильно дает превышение в несколько десятых градуса - видимо, есть-таки пусть и небольшой, но нагрев от микроконтроллера.

step962
Offline
Зарегистрирован: 23.05.2011

ua6em:

Ну, то что в коробочке валялось. BluePill был выбран в качестве основы за наличие на его борту часов, а esp8266 (в виде NodeMCU) - в качестве связующего звена между основным микроконтроллером и USB-модемом, который умел общаться с окрестностями только по вайфаю. А до ESP32 у меня руки пока не дошли.

В коробочке не нашел ;)

step962
Offline
Зарегистрирован: 23.05.2011

Igor_116

Схему увидеть можно, только от картинки пользы никакой - создана она в Eagle и все сигналы выведены в шину. Так что какой проводок откуда и куда идет, понять невозможно:

Вот здесь можно загрузить проект (ссылка "Eagle-проект метеостанции") и исследовать связи средствами "Орла".

Там же загружается и библиотека MD_Scout, необходимая для компиляции скетча из первого сообщения.

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

sadman41
Offline
Зарегистрирован: 19.10.2016

У меня вопрос: в чем заключается проблема существования отклонения на несколько десятых градуса при нахождении измерителя на улице либо в помещении?

Igor_116
Offline
Зарегистрирован: 09.11.2017

step962

ЧТО МЕШАЕТ СЕМУ ВЫЛОЖИТЬ В ФОРМАТЕ Eagle

step962
Offline
Зарегистрирован: 23.05.2011

Igor_116 пишет:

step962

ЧТО МЕШАЕТ СЕМУ ВЫЛОЖИТЬ В ФОРМАТЕ Eagle

Да я уже написал выше: бессмысленность этого действия. Вот (кликабельно):

а если имеется ввиду файл *.sch, то опять-же, выше была приведена ссылка на страницу, с которой можно скачать весь Eagle-проект - там и *.sch есть, и *.brd

step962
Offline
Зарегистрирован: 23.05.2011

sadman41 пишет:
...в чем заключается проблема существования отклонения на несколько десятых градуса при нахождении измерителя на улице либо в помещении?

Я полагаю, в близости "печки", то есть микроконтроллера. "Измеритель в помещении" - это модули bmp180 и dht22, расположенные на той же плате, что и Blue Pill. "Измеритель на улице" - это датчик, отнесенный от основной платы на длину провода, т.е. около 1 м.

На новом варианте метеостанции, там, где у меня на плате только bme280, а на метровых проводах два термометра ds18b20, я баловался с различным положением этих термометров. Туго привязанные друг к другу резинкой, они давали отклонение в измерениях не более 0,2 градуса. Иногда... Чаще это было отклонение 0,0. То есть даже меньше погрешности

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

Как-то так...

AndreyD
AndreyD аватар
Offline
Зарегистрирован: 07.10.2018

step962 пишет:

Вот, нашёл статейку, которую я читал перед покупкой BME280.

https://vladikoms.livejournal.com/97796.html

 

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

 

Igor_116
Offline
Зарегистрирован: 09.11.2017

 

step962

если имеется ввиду файл *.sch, то опять-же, выше была приведена ссылка на страницу, с которой можно скачать весь Eagle-проект - там и *.sch есть, и *.brd

из дома ссылка почему-то не открывается. сейчас на работе открыл

 

sadman41
Offline
Зарегистрирован: 19.10.2016

То, что BME/BMP "наловит" температуру от МК или DC-DC - это понятно, он так же ее покажет "неправильно" и на сквозняке.

Я не понимаю чем это мешает в жизни - увидеть вместо 23 градусов 23.4, к примеру. Это же не су-вид, не камера для выращивания разумной плесени.
Кстати, то, что DS-ки показывают одинаковую температуру не говорит о том, что эта температура истинна. Просто отклонения удачно совпадают.

У меня BMP280, МК и DC-DC (+ещё перефирия) находятся в корпусе щитка для электрических автоматов. DC-DC в верхней части, МК посередине, BMP в нижней. По 10 см от каждого от источников тепла. Конвекцией все тепловые "наводки"выносятся вверх, поэтому датчик температуру особо не искажает. А ловить десятые градуса при оценке состояния окружающей среды таким измерителем - это бессмыслица.

step962
Offline
Зарегистрирован: 23.05.2011

Перед тем как перейти к представлению следующего шага в развитии приложения, размещу здесь скриншот с экрана планшета, относящийся к стартовому варианту приложения (далее ШАГ_0):

step962
Offline
Зарегистрирован: 23.05.2011

ШАГ 1:

Перерыв между шагами 0 и 1 оказался длиннее, чем я предполагал: сначала во время тестовых прогонов выяснилось, что в MoDyz при перерисовке экранов происходило создание все новых экземпляров элементов управления, создаваемых по командам из Arduino-скетча. К каким-то катастрофическим последствиям это не приводило, приложение корректно отрабатывало все прикосновения к этим элементам. Только после перехода к генерации нескольких экранов (а на шаге 1 их стало три - стартовый экран, экран редактирования даты и экран редактирования времени) стало очевидным, что простое стирание изображения удаляет только графику, но не кнопки, поля ввода и прочие элементы управления из инструментария Java.

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

Что нового появилось на этом этапе?

MoDyz

На стартовом экране появились две кнопки, предназначенные для вызова экранов настройки даты и времени. Соответственно, добавилось два новых очень простеньких экрана - один для настройки даты, другой для настройки времени. Эти новшества относятся не столько к MoDyz, сколько к скетчу Arduino: программные коды на стороне Андроида не претерпели изменений, все соответствующие функции активируются из скетча Arduino.

Вот так выглядит обновленный стартовый экран (на нем появились поля для отображения даты и времени и две кнопки для вызова экранов настройки):

и два экрана настройки (по три поля вывода текста на каждом и по одному полю ввода):

. .

Загрузить приложение для Андроида можно здесь.

Arduino (STM32)

Скетч пополнился функциями работы с часами, встроенными в микроконтроллер STM32F103xxxx, а также блоками обработки команд, связанных с установкой и чтением даты/времени. Эти команды могут поступать по трем каналам - Serial (от ПК), Serial1+Bluetooth (от смартфона/планшета) и Serial2 (от WiFi-моста на базе esp8266, задачей которого будет организация выхода в интернет). Соответственно, расширилась функция генерации стартового экрана (showMain) и появилась функция генерации экранов настройки (showRTCSetup).

Новая редакция скетча:

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *  Метеостанция - дальнейшее развитие скетча STM32_Meteo_0. Добавлены функции *
 *  вывода в COM-порт и на Android-устройство даты/времени, настройки          *
 *  даты/времени                                                               *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

// работа с bme280
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

// работа с ds18b20
//#include <OneWire.h>
#include <OneWireSTM.h>
#define DS_DATA_PIN0 PB8
#define DS_DATA_PIN1 PB9

#include <RTClock.h>
#define BASE_YEAR 1970

// модули поддержки работы с MoDyz == (C\Users\SAP\Documents\Arduino\libraries\MD_Scout) ==
#include <mdwriter.h>
#include <mdsender_bt.h>

#define LOG_SLICE 10
#define _WITH_SER1

// независимое питание часов (3V) - батарейка CR2032, подключаемая к выводам Gnd и VB
// Serial (USB) - интерфейс с компьютером, при инициализации скорость порта не указывается
//                (USB!), терминальная программа может работать на любой скорости
// Serial1 (PA9-TX,PA10-RX) - интерфейс с модулем Bluetooth на скорости последовательного
//                порта по умолчанию (9600 бод). Модуль spp-c без проблем коммуницирует на
//                такой скорости
// set up variables using the SD utility library functions:

int inByte;
long nextMillis;
long Pascals, mmHg;
float bme_t, bme_h, ds_t[2], Tp;
float t_min[3], t_max[3], p_min, p_max, h_min, h_max;

RTClock rtclock (RTCSEL_LSE); // работа часов от внешнего кварца
tm_t mtt;
char s[128]; // буфер для sprintf

Adafruit_BME280 bme; // use I2C interface

OneWire  ds0(DS_DATA_PIN0);
OneWire  ds1(DS_DATA_PIN1);
MD_Sender_BT mdScout;
String sResponse, sCommand[3];

void setup() {
  delay(2000); // задержка для реинициализации USB-порта
  // initialize the digital pin as an output. // ???
  pinMode(PC13, OUTPUT);                      // ???
  Serial.begin();  // USB does not require BAUD
  Serial1.begin(9600);  // Канал для связи с модулем Bluetooth
  Serial2.begin(9600);  // Канал для связи с модулем ESP8266 (NodeMCU)
  Serial.println( "+-----------------------------------+");
  Serial.println( "| ========= STM32_Meteo_0 ========= |");
  Serial.println( "+-----------------------------------+");
  Serial1.println("+-----------------------------------+");
  Serial1.println("| ========= STM32_Meteo_0 ========= |");
  Serial1.println("+-----------------------------------+");

  for(int i=0;i<3;i++) {t_min[i] = 1000; t_max[i] = -1000; }
  p_min = 9999; p_max = -1000;
  h_min = 1000; h_max = -1000;
  
  nextMillis = millis();  // первое считывание информации с датчиков - сразу после старта
  Serial.println("Initialize BME280...");
  if (!bme.begin()) {
    Serial.println(F("Could not find a valid BME280 sensor, check wiring!"));
    while (1) delay(10);
  }
} // of setup

void loop() {
  char c, i;

  readHwSerial(Serial1,sCommand[1],1);  // поток данных от модуля Bluetooth
  readHwSerial(Serial2,sCommand[2],2);  // поток данных от модуля ESP8266 (NodeMCU)
  Serial_getchar(sCommand[0]);          // данные и команды, поступающие в порт Serial (терминал)
  
  if((long)(nextMillis-millis())<=0) {
    nextMillis = nextMillis + 10000;  // опрос датчиков  - раз в 10 секунд
    getData();            // считывание показаний с датчиков
    sendDataToTerminal(); // вывод информации в терминал
  }
} // of loop

// ~~~ Функции работы с часами реального времени ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void settime(int hr,int min,int sec) {
  rtclock.breakTime(rtclock.now(), mtt);
  mtt.hour   = hr;
  mtt.minute = min;
  mtt.second = sec;
  rtclock.setTime(mtt); 
}

void setdate(int day,int month,int year) {
  rtclock.breakTime(rtclock.now(), mtt);
  mtt.day   = day;
  mtt.month = month;
  mtt.year  = year-BASE_YEAR;
  rtclock.setTime(mtt); 
}

String timeToBuffer() {
  rtclock.breakTime(rtclock.now(), mtt);
  sprintf(s, "%02u:%02u:%02u",mtt.hour, mtt.minute, mtt.second);
  return String(s);
}

String dateToBuffer() {
  rtclock.breakTime(rtclock.now(), mtt);
  sprintf(s, "%02u/%02u/%04u",mtt.day, mtt.month, mtt.year+BASE_YEAR);
  return String(s);
}

void setNewTime(String sCmd) {
  Serial.print("Установка нового времени: ");
  int p=sCmd.indexOf(',');
  int l=sCmd.length();
  Serial.print(p);  Serial.print(" - ");
  Serial.print(l);  Serial.print(" -> ");
  if(p>=0) {
    Serial.println(sCmd.substring(p+1,l-1));
    settime(sCmd.substring(p+1,p+3).toInt(),
            sCmd.substring(p+4,p+6).toInt(),
            sCmd.substring(p+7,p+9).toInt());
  }
  else
    Serial.println();
} // of setNewTime

void setNewDate(String sCmd) {
  Serial.print("Установка новой даты: ");
  int p=sCmd.indexOf(',');
  int l=sCmd.length();
  Serial.print(p);  Serial.print(" - ");
  Serial.print(l);  Serial.print(" -> ");
  if(p>=0) {
    Serial.println(sCmd.substring(p+1,l-1));
    setdate(sCmd.substring(p+1,p+3).toInt(),
            sCmd.substring(p+4,p+6).toInt(),
            sCmd.substring(p+7,p+11).toInt());
  }
  else
    Serial.println();
} // of setNewDate

/* Установка нового времени, определенного в строке формата "settime hh:mm:ss" */
void cmd_settime(String sCmd) {
  int p,hr,min,sec;
  p = sCmd.indexOf("settime")+8; // длина команды плюс пробел
  hr  = sCmd.substring(p+0, p+2).toInt();
  min = sCmd.substring(p+3, p+5).toInt();
  sec = sCmd.substring(p+6, p+8).toInt();
  settime(hr,min,sec);
}

/* Установка новой даты, определенной в строке формата "setdate dd/mm/yy", где yy - две цифры года 21-го века */
void cmd_setdate(String sCmd) {
  int p,d,m,y;
  p = sCmd.indexOf("setdate")+8; // длина команды плюс пробел
  d = sCmd.substring(p+0, p+2).toInt();
  m = sCmd.substring(p+3, p+5).toInt();
  y = sCmd.substring(p+6, p+8).toInt();
  y=y+2000;
  Serial.print("========= setting date to: "); Serial.print(d);  Serial.print("/");
  Serial.print(m);  Serial.print("/"); Serial.println(y);
  setdate(d,m,y);
}

void cmd_gettime() {
  timeToBuffer();
  Serial.println(s);
  Serial1.println(s);
}

void cmd_getdate() {
  dateToBuffer();
  Serial.println(s);
  Serial1.println(s);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void readTemp(OneWire ds,float &t) {
  byte present = 0;
  byte i;
  byte addr[8];
  byte data[12];
  float celsius;

  ds.reset_search();
  
  if(!ds.search(addr)) {
    Serial.println("No more addresses.");
    Serial.println();
    ds.reset_search();
    delay(250);
    return;
  }
  
  if(OneWire::crc8(addr, 7) != addr[7]) {
    Serial.println("CRC is not valid!!!");
    delay(250);
    return;
  }
  ds.reset();
  ds.select(addr);
  ds.write(0x44, 1);
  delay(1000); // ожидание завершения цикла измерения
  present = ds.reset();
  ds.select(addr);
  ds.write(0xBE);

  for(i=0;i<9;i++) data[i] = ds.read();

  int16_t raw = (data[1]<<8) | data[0];
  byte cfg = (data[4] & 0x60);
  if(cfg==0x00) raw = raw & ~7;
  else if(cfg==0x20) raw = raw & ~3;
  else if(cfg==0x40) raw = raw & ~1;
  celsius = (float)raw/16.0;
  t = celsius;
} // of readTemp

float calculateDevPoint(float t,float h) {
  float a=17.27,b=237.7,v;
  v = a*t/(b+t)+log(h/100);
  Tp=b*v/(a-v);
  return Tp;
} // of calculateDevPoint

void calcMinMax(float val, float *min_val, float *max_val) {
  if(*min_val>val) *min_val = val;
  if(*max_val<val) *max_val = val;
} // of calcMinMax

void getData() {
// чтение температуры, давления и влажности с модуля bme280
    bme_t = bme.readTemperature();
    Pascals = bme.readPressure();///100.0;
    bme_h = bme.readHumidity();
    mmHg = round(Pascals / 133.3223684 * 10);

    calcMinMax(bme_t,&t_min[0],&t_max[0]);
    calcMinMax(bme_h,&h_min,&h_max);
    calcMinMax(mmHg,&p_min,&p_max);

// чтение температуры с датчиков ds18b20
    readTemp(ds0,ds_t[0]);
    readTemp(ds1,ds_t[1]);
    calcMinMax(ds_t[0],&t_min[1],&t_max[1]);
    calcMinMax(ds_t[1],&t_min[2],&t_max[2]);
  calculateDevPoint(bme_t,bme_h);
} // of getData

void sendDataToTerminal() {
  Serial.print("==== mTime: "); Serial.println(millis());
  Serial.print(F("Temperature.......: ")); Serial.print(bme_t, 1);      Serial.print(F(" +-1.0°C, "));
  Serial.print(F("Pressure..........: ")); Serial.print(Pascals);       Serial.print(F(" +-100Pa"));
  Serial.print(F(" ("));                   Serial.print(mmHg/10.0, 1);  Serial.println(F(" mmHg)"));
  Serial.print(p_min/10.0,1);              Serial.print("  ");          Serial.println(p_max/10.0,1);
  Serial.print(F("Humidity.......: "));    Serial.print(bme_h, 0);      Serial.println(F(" +-2%"));
  Serial.print(h_min,0);                   Serial.print("  ");          Serial.println(h_max,0);
  Serial.print(F("Temp in...........: ")); Serial.print(ds_t[0], 1);   Serial.println(F(" +-0.5°C"));
  Serial.print(F("Temp out..........: ")); Serial.print(ds_t[1], 1);   Serial.println(F(" +-0.5°C"));
  Serial.print(F("Dev.point.........: ")); Serial.print(Tp, 1);        Serial.println(F(" +-0.4°C"));
  Serial.flush();
} // of sendDataToTerminal

void readHwSerial(HardwareSerial S, String &sCmd, byte PortNum) {
  char c;
    if (S.available()) {
    inByte = S.read();
    Serial.write(inByte);
// работа с MoDyz =======================
    c = inByte;
    if(c=='\r')       interpreteCommand(sCmd, PortNum);
    else if(c=='\n')  interpreteCommand(sCmd, PortNum);
    else sCmd = sCmd + c;
  }
} // of readHwSerial

/* чтение очередного символа, поступившего по UART
 *  
 */
void Serial_getchar(String &sCmd) {
  if (Serial.available()) {
    inByte = Serial.read();
    Serial1.write(inByte);
    if (inByte==0x0A)       interpreteCommand(sCmd, 0);//interpreteCmd();
    else if (inByte==0x0D)  interpreteCommand(sCmd, 0);//interpreteCmd();
    else sCmd = sCmd + (char)inByte;
  }
}

void sendString() {
  for(int i=0;i<sResponse.length();i++)
    Serial1.write(sResponse.charAt(i));
  Serial1.write('\r'); Serial1.write('\n');
  sResponse = "";
} // of sendString

void addCmdString(String sCmd) {
  sResponse += sCmd;
  if(sResponse.length()>100) sendString();
} // of addCmdString

void showMain() {
  // координаты оптимизированы для экрана 800x1280
  sResponse = "";
  
  addCmdString(mdScout.setDisplayColors(C_BLACK,C_LTGRAY));
  addCmdString(mdScout.fillDisplayFG());

  addCmdString(mdScout.setLineColors(6/*C_CYAN*/,C_MAGENTA));
  addCmdString(mdScout.fillRectangleBG(150,80,680,440));
  addCmdString(mdScout.drawRectangle(148,78,682,442));
  addCmdString(mdScout.drawRectangle(152,82,678,438));
  
  //addCmdString(mdScout.setTextColors(C_RED,C_LTGRAY));
  addCmdString(mdScout.setTextSize(40));

  addCmdString(mdScout.setTextColors(C_BLUE,C_LTGRAY));
  addCmdString(mdScout.outTextR(410,170,"BME280:"));
  addCmdString(mdScout.outTextL(430,170,String(bme_t,1) + " °C"));
  addCmdString(mdScout.outTextL(430,220,String(mmHg/10.0,1) + " mmHg"));

  addCmdString(mdScout.setTextColors(C_CYAN,C_LTGRAY));
  addCmdString(mdScout.outTextR(410,270,"BME280:"));
  addCmdString(mdScout.outTextL(430,270,String(bme_h,0) + " %"));
  
  addCmdString(mdScout.setTextColors(C_RED,C_LTGRAY));
  addCmdString(mdScout.outTextR(410,320,"ds18:"));
  addCmdString(mdScout.outTextL(430,320,String(ds_t[0],1) + " °C"));
  addCmdString(mdScout.outTextL(430,370,String(ds_t[1],1) + " °C"));
  addCmdString(mdScout.setTextColors(C_YELLOW,C_LTGRAY));
  addCmdString(mdScout.outTextR(410,420,"DevPt:"));
  addCmdString(mdScout.outTextL(430,420,String(Tp,1) + " °C"));
// вывод времени в правом верхнем углу
  addCmdString(mdScout.setTextColors(C_YELLOW,C_BLUE));
  addCmdString(mdScout.setTextSize(30));
  addCmdString(mdScout.outTextL(170,90,dateToBuffer()));
  addCmdString(mdScout.outTextR(660,90,timeToBuffer()));
// определение кнопок для редактирования даты и времени
  addCmdString(mdScout.setTextColors(C_GREEN,C_BLUE));
  addCmdString(mdScout.setLineColors(C_YELLOW,C_RED));
  addCmdString(mdScout.defineTouchButton(250,470,101,111,"   Дата   "));
  addCmdString(mdScout.defineTouchButton(450,470,102,112,"  Время   "));

  sendString();
} // of showMain

void showRTCSetup(bool setupTime) {
  String curTime,curDate;
  byte i;
  curTime = timeToBuffer();
  curDate = dateToBuffer();
  addCmdString(mdScout.setDisplayColors(C_BLACK,C_LTGRAY));
  addCmdString(mdScout.setLineColors(3/*C_RED*/,C_RED));
  addCmdString(mdScout.fillDisplayFG());
  addCmdString(mdScout.drawRectangle(130,40,660,180));
//  addCmdString(mdScout.setLineColors(C_LTGRAY,C_MAGENTA));
//  addCmdString(mdScout.fillRectangleFG(50,20,350,60));
//  addCmdString(mdScout.fillRectangleBG(400,20,700,60));

/*  for(i=0;i<16;i++) {
    addCmdString(mdScout.setLineColors(i,(i+5)%16));
    addCmdString(mdScout.drawLine(130,200+i*5,660,200+i*20));
  } */

  addCmdString(mdScout.setTextSize(40));
  addCmdString(mdScout.setTextColors(C_BLUE,C_BLACK));
  if(setupTime) {
    addCmdString(mdScout.outTextR(450,80,"Текущее время: "));
    addCmdString(mdScout.outTextL(470,80,curTime));
    addCmdString(mdScout.outTextR(450,140,"Новое время: "));
    addCmdString(mdScout.setTextColors(C_YELLOW,C_BLUE));
    addCmdString(mdScout.defineEditText(1,470,110,120,40));
    addCmdString(mdScout.setEditTextStr(1,curTime));
  }
  else {
    addCmdString(mdScout.outTextR(430,80,"Текущая дата: "));
    addCmdString(mdScout.outTextL(450,80,curDate));
    addCmdString(mdScout.outTextR(430,140,"Новая дата: "));
    addCmdString(mdScout.setTextColors(C_YELLOW,C_BLUE));
    addCmdString(mdScout.defineEditText(2,450,110,160,40));
    addCmdString(mdScout.setEditTextStr(2,curDate));
  }

  sendString();
} // of showRTCSetup

void interpreteCommand(String &sCmd,byte PortNum) {
  if(sCmd.length()>0) {
    Serial.print("Interprete command: ["); Serial.print(sCmd); Serial.println("]");
    if(sCmd.equals("redrw"))    showMain();     // на смартфоне была нажата кнопка "redrw"
    else if(sCmd.equals("drw")) showMain();     // на смартфоне была нажата кнопка "drw"
    else if(sCmd.equals("101")) showRTCSetup(false);
    else if(sCmd.equals("102")) showRTCSetup(true);
    else if(sCmd.indexOf("#FE1")>=0) {
      setNewTime(sCmd);
      showMain();
    }
    else if(sCmd.indexOf("#FE2")>=0) {
      setNewDate(sCmd);
      showMain();
    }
    else if(sCmd.indexOf("settime")>=0) cmd_settime(sCmd); 
    else if(sCmd.indexOf("gettime")>=0) cmd_gettime(); 
    else if(sCmd.indexOf("setdate")>=0) cmd_setdate(sCmd);
    else if(sCmd.indexOf("getdate")>=0) cmd_getdate();
    else if(sCmd.equals("fun1"));
    // ...
    else if(sCmd.equals("fun6"));
    sCmd = "";
  }
} // of interpreteCommand

 

 

step962
Offline
Зарегистрирован: 23.05.2011

Шаг 2:

/* * * * * * * * * # * * * * * * * * * # * * * * * * * * * # * * * * * * * * * *
 *  Метеостанция - дальнейшее развитие скетча STM32_Meteo_1. Добавлены функции *
 *  сохранения результатов измерений в файлах журнала, вывода содержимого ука- *
 *  занного файла на экран смартфона, передачи по каналам последовательного    *
 *  доступа                                                                    *
 * * * * * * * * * # * * * * * * * * * # * * * * * * * * * # * * * * * * * * * */

// работа с bme280
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

// работа с ds18b20
#include <OneWireSTM.h>
#define DS_DATA_PIN0 PB8
#define DS_DATA_PIN1 PB9

#include <RTClock.h>
#define BASE_YEAR 1970
// include the SD library:
#include <SPI.h>
#include <SD.h>

// модули поддержки работы с MoDyz == (C\Users\SAP\Documents\Arduino\libraries\MD_Scout) ==
#include <mdwriter.h>
#include <mdsender_bt.h>

#define LOG_SLICE 10
#define LOG_PERIOD 600000 // запись показаний датчиков в журнал каждые 10 минут
#define _WITH_SER1

// независимое питание часов (3V) - батарейка CR2032, подключаемая к выводам Gnd и VB
// Serial (USB) - интерфейс с компьютером, при инициализации скорость порта не указывается
//                (USB!), терминальная программа может работать на любой скорости
// Serial1 (PA9-TX,PA10-RX) - интерфейс с модулем Bluetooth на скорости последовательного
//                порта по умолчанию (9600 бод). Модуль spp-c без проблем коммуницирует на
//                такой скорости
// set up variables using the SD utility library functions:

int inByte;
long nextMillis, nextLogMillis;
long Pascals, mmHg;
float bme_t, bme_h, ds_t[2], Tp;
float t_min[3], t_max[3], p_min, p_max, h_min, h_max;

RTClock rtclock (RTCSEL_LSE); // работа часов от внешнего кварца
tm_t mtt;
char s[128]; // буфер для sprintf

Adafruit_BME280 bme; // use I2C interface

OneWire  ds0(DS_DATA_PIN0);
OneWire  ds1(DS_DATA_PIN1);
MD_Sender_BT mdScout;
String sResponse, sCommand[3];

void setup() {
  delay(2000); // задержка для реинициализации USB-порта
  // initialize the digital pin as an output. // ???
  pinMode(PC13, OUTPUT);                      // ???
  Serial.begin();  // USB does not require BAUD
  Serial1.begin(9600);  // Канал для связи с модулем Bluetooth
  Serial2.begin(9600);  // Канал для связи с модулем ESP8266 (NodeMCU)
  Serial.println( "+-----------------------------------+");
  Serial.println( "| ========= STM32_Meteo_2 ========= |");
  Serial.println( "+-----------------------------------+");
  Serial1.println("+-----------------------------------+");
  Serial1.println("| ========= STM32_Meteo_2 ========= |");
  Serial1.println("+-----------------------------------+");

  for(int i=0;i<3;i++) {t_min[i] = 1000; t_max[i] = -1000; }
  p_min = 9999; p_max = -1000;
  h_min = 1000; h_max = -1000;
  
  SD.begin(); // !!! #### без этой инициализации файловые операции не работают #### !!!

  nextMillis = millis();  // первое считывание информации с датчиков - сразу после старта
  nextLogMillis = nextMillis;
  Serial.println("Initialize BME280...");
  if (!bme.begin()) {
    Serial.println(F("Could not find a valid BME280 sensor, check wiring!"));
    while (1) delay(10);
  }
} // of setup

void loop() {
  char c, i;

  readHwSerial(Serial1,sCommand[1],1);  // поток данных от модуля Bluetooth
  readHwSerial(Serial2,sCommand[2],2);  // поток данных от модуля ESP8266 (NodeMCU)
  Serial_getchar(sCommand[0]);          // данные и команды, поступающие в порт Serial (терминал)
  
  if((long)(nextMillis-millis())<=0) {
    nextMillis = nextMillis + 10000;  // опрос датчиков  - раз в 10 секунд
    getData();            // считывание показаний с датчиков
    sendDataToTerminal(); // вывод информации в терминал
  }
  if((long)(nextLogMillis-millis())<=0) {
    nextLogMillis = nextLogMillis+LOG_PERIOD;
    saveDataToLog();
  }
} // of loop

// ~~~ ~~~~~~~~~~~
void saveDataToLog() {
  File logFile;
//  long Pascals, mmHg;

  Serial.println("================= writing to log-file =========================");
  rtclock.breakTime(rtclock.now(), mtt);
  //Pascals = myBMP.getPressure();
  //mmHg = round(Pascals / 133.3223684 * 10);

#ifdef WITH_7SEG
  if (menueActivated) {
    sResponse = "";
    addCmdString(mdScout.setValue7SegmentDisplay(0,mmHg));
    //addCmdString(mdScout.setValue7SegmentDisplay(1,(int)(myBMP.getTemperature()*10)));
    addCmdString(mdScout.setValue7SegmentDisplay(1,(int)(bmp_t*10)));
    sendString();
  }
#endif
  
  sprintf(s, "%02u%02u%02u.met", mtt.year+BASE_YEAR, mtt.month, mtt.day);
  Serial.println(s);
  logFile = SD.open(s, FILE_WRITE);
  if(logFile) {
//    sprintf(s, "%02u:%02u:%02u tin=%5.1f tout=%5.1f p=%5.1f h=%5.0f", 
//          mtt.hour, mtt.minute, mtt.second,
//          (bme_t+ds_t[0])/2, ds_t[1], mmHg/10.0, bme_h);
    sprintf(s, "%02u:%02u:%02u tin=%5.1f tout1=%5.1f tout2=%5.1f p=%5.1f h=%5.0f", 
          mtt.hour, mtt.minute, mtt.second,
          bme_t, ds_t[0], ds_t[1], mmHg/10.0, bme_h);
    Serial.println(s);
    logFile.println(s);
    logFile.close();
  }
  else {
    sprintf(s, "%02u%02u%02u.met", mtt.year+BASE_YEAR, mtt.month, mtt.day);
    Serial.print("error opening file ");
    Serial.println(s);
  }
  
  logFile = SD.open("meteo.tst", FILE_WRITE);
  if(logFile) {
    sprintf(s, "%02u/%02u/%04u ",mtt.day, mtt.month, mtt.year+BASE_YEAR);
    logFile.print(s);
    sprintf(s, "%02u:%02u:%02u",mtt.hour, mtt.minute, mtt.second);
    logFile.print(s);
    logFile.println("   ==============");
    logFile.close();
  }
  else
    Serial.print("error opening file!!!!");
} // of saveDataToLog

// ~~~ Функции работы с часами реального времени ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void settime(int hr,int min,int sec) {
  rtclock.breakTime(rtclock.now(), mtt);
  mtt.hour   = hr;
  mtt.minute = min;
  mtt.second = sec;
  rtclock.setTime(mtt); 
}

void setdate(int day,int month,int year) {
  rtclock.breakTime(rtclock.now(), mtt);
  mtt.day   = day;
  mtt.month = month;
  mtt.year  = year-BASE_YEAR;
  rtclock.setTime(mtt); 
}

String timeToBuffer() {
  rtclock.breakTime(rtclock.now(), mtt);
  sprintf(s, "%02u:%02u:%02u",mtt.hour, mtt.minute, mtt.second);
  return String(s);
}

String dateToBuffer() {
  rtclock.breakTime(rtclock.now(), mtt);
  sprintf(s, "%02u/%02u/%04u",mtt.day, mtt.month, mtt.year+BASE_YEAR);
  return String(s);
}

void setNewTime(String sCmd) {
  Serial.print("Установка нового времени: ");
  int p=sCmd.indexOf(',');
  int l=sCmd.length();
  Serial.print(p);  Serial.print(" - ");
  Serial.print(l);  Serial.print(" -> ");
  if(p>=0) {
    Serial.println(sCmd.substring(p+1,l-1));
    settime(sCmd.substring(p+1,p+3).toInt(),
            sCmd.substring(p+4,p+6).toInt(),
            sCmd.substring(p+7,p+9).toInt());
  }
  else
    Serial.println();
} // of setNewTime

void setNewDate(String sCmd) {
  Serial.print("Установка новой даты: ");
  int p=sCmd.indexOf(',');
  int l=sCmd.length();
  Serial.print(p);  Serial.print(" - ");
  Serial.print(l);  Serial.print(" -> ");
  if(p>=0) {
    Serial.println(sCmd.substring(p+1,l-1));
    setdate(sCmd.substring(p+1,p+3).toInt(),
            sCmd.substring(p+4,p+6).toInt(),
            sCmd.substring(p+7,p+11).toInt());
  }
  else
    Serial.println();
} // of setNewDate

/* Установка нового времени, определенного в строке формата "settime hh:mm:ss" */
void cmd_settime(String sCmd) {
  int p,hr,min,sec;
  p = sCmd.indexOf("settime")+8; // длина команды плюс пробел
  hr  = sCmd.substring(p+0, p+2).toInt();
  min = sCmd.substring(p+3, p+5).toInt();
  sec = sCmd.substring(p+6, p+8).toInt();
  settime(hr,min,sec);
}

/* Установка новой даты, определенной в строке формата "setdate dd/mm/yy", где yy - две цифры года 21-го века */
void cmd_setdate(String sCmd) {
  int p,d,m,y;
  p = sCmd.indexOf("setdate")+8; // длина команды плюс пробел
  d = sCmd.substring(p+0, p+2).toInt();
  m = sCmd.substring(p+3, p+5).toInt();
  y = sCmd.substring(p+6, p+8).toInt();
  y=y+2000;
  Serial.print("========= setting date to: "); Serial.print(d);  Serial.print("/");
  Serial.print(m);  Serial.print("/"); Serial.println(y);
  setdate(d,m,y);
}

void cmd_gettime() {
  timeToBuffer();
  Serial.println(s);
  Serial1.println(s);
}

void cmd_getdate() {
  dateToBuffer();
  Serial.println(s);
  Serial1.println(s);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void readTemp(OneWire ds,float &t) {
  byte present = 0;
  byte i;
  byte addr[8];
  byte data[12];
  float celsius;

  ds.reset_search();
  
  if(!ds.search(addr)) {
    Serial.println("No more addresses.");
    Serial.println();
    ds.reset_search();
    delay(250);
    return;
  }
  
  if(OneWire::crc8(addr, 7) != addr[7]) {
    Serial.println("CRC is not valid!!!");
    delay(250);
    return;
  }
  ds.reset();
  ds.select(addr);
  ds.write(0x44, 1);
  delay(1000); // ожидание завершения цикла измерения
  present = ds.reset();
  ds.select(addr);
  ds.write(0xBE);

  for(i=0;i<9;i++) data[i] = ds.read();

  int16_t raw = (data[1]<<8) | data[0];
  byte cfg = (data[4] & 0x60);
  if(cfg==0x00) raw = raw & ~7;
  else if(cfg==0x20) raw = raw & ~3;
  else if(cfg==0x40) raw = raw & ~1;
  celsius = (float)raw/16.0;
  t = celsius;
} // of readTemp

float calculateDevPoint(float t,float h) {
  float a=17.27,b=237.7,v;
  v = a*t/(b+t)+log(h/100);
  Tp=b*v/(a-v);
  return Tp;
} // of calculateDevPoint

void calcMinMax(float val, float *min_val, float *max_val) {
  if(*min_val>val) *min_val = val;
  if(*max_val<val) *max_val = val;
} // of calcMinMax

void getData() {
// чтение температуры, давления и влажности с модуля bme280
    bme_t = bme.readTemperature();
    Pascals = bme.readPressure();///100.0;
    bme_h = bme.readHumidity();
    mmHg = round(Pascals / 133.3223684 * 10);

    calcMinMax(bme_t,&t_min[0],&t_max[0]);
    calcMinMax(bme_h,&h_min,&h_max);
    calcMinMax(mmHg,&p_min,&p_max);

// чтение температуры с датчиков ds18b20
    readTemp(ds0,ds_t[0]);
    readTemp(ds1,ds_t[1]);
    calcMinMax(ds_t[0],&t_min[1],&t_max[1]);
    calcMinMax(ds_t[1],&t_min[2],&t_max[2]);
  calculateDevPoint(bme_t,bme_h);
} // of getData

void sendDataToTerminal() {
  Serial.print("==== mTime: "); Serial.println(millis());
  Serial.print(F("Temperature.......: ")); Serial.print(bme_t, 1);      Serial.print(F(" +-1.0°C, "));
  Serial.print(F("Pressure..........: ")); Serial.print(Pascals);       Serial.print(F(" +-100Pa"));
  Serial.print(F(" ("));                   Serial.print(mmHg/10.0, 1);  Serial.println(F(" mmHg)"));
  Serial.print(p_min/10.0,1);              Serial.print("  ");          Serial.println(p_max/10.0,1);
  Serial.print(F("Humidity.......: "));    Serial.print(bme_h, 0);      Serial.println(F(" +-2%"));
  Serial.print(h_min,0);                   Serial.print("  ");          Serial.println(h_max,0);
  Serial.print(F("Temp in...........: ")); Serial.print(ds_t[0], 1);   Serial.println(F(" +-0.5°C"));
  Serial.print(F("Temp out..........: ")); Serial.print(ds_t[1], 1);   Serial.println(F(" +-0.5°C"));
  Serial.print(F("Dev.point.........: ")); Serial.print(Tp, 1);        Serial.println(F(" +-0.4°C"));
  Serial.flush();
} // of sendDataToTerminal

void readHwSerial(HardwareSerial S, String &sCmd, byte PortNum) {
  char c;
    if (S.available()) {
    inByte = S.read();
    Serial.write(inByte);
// работа с MoDyz =======================
    c = inByte;
    if(c=='\r')       interpreteCommand(sCmd, PortNum);
    else if(c=='\n')  interpreteCommand(sCmd, PortNum);
    else sCmd = sCmd + c;
  }
} // of readHwSerial

/* чтение очередного символа, поступившего по UART
 *  
 */
void Serial_getchar(String &sCmd) {
  if (Serial.available()) {
    inByte = Serial.read();
    Serial1.write(inByte);
    if (inByte==0x0A)       interpreteCommand(sCmd, 0);//interpreteCmd();
    else if (inByte==0x0D)  interpreteCommand(sCmd, 0);//interpreteCmd();
    else sCmd = sCmd + (char)inByte;
  }
}

void sendString() {
  for(int i=0;i<sResponse.length();i++)
    Serial1.write(sResponse.charAt(i));
  Serial1.write('\r'); Serial1.write('\n');
  sResponse = "";
} // of sendString

void addCmdString(String sCmd) {
  sResponse += sCmd;
  if(sResponse.length()>100) sendString();
} // of addCmdString

void showMain() {
  // координаты оптимизированы для экрана 800x1280
  sResponse = "";
  
  addCmdString(mdScout.setDisplayColors(C_BLACK,C_LTGRAY));
  addCmdString(mdScout.fillDisplayFG());

  addCmdString(mdScout.setLineColors(6/*C_CYAN*/,C_MAGENTA));
  addCmdString(mdScout.fillRectangleBG(150,80,680,440));
  addCmdString(mdScout.drawRectangle(148,78,682,442));
  addCmdString(mdScout.drawRectangle(152,82,678,438));
  
  //addCmdString(mdScout.setTextColors(C_RED,C_LTGRAY));
  addCmdString(mdScout.setTextSize(40));

  addCmdString(mdScout.setTextColors(C_BLUE,C_LTGRAY));
  addCmdString(mdScout.outTextR(410,170,"BME280:"));
  addCmdString(mdScout.outTextL(430,170,String(bme_t,1) + " °C"));
  addCmdString(mdScout.outTextL(430,220,String(mmHg/10.0,1) + " mmHg"));

  addCmdString(mdScout.setTextColors(C_CYAN,C_LTGRAY));
  addCmdString(mdScout.outTextR(410,270,"BME280:"));
  addCmdString(mdScout.outTextL(430,270,String(bme_h,0) + " %"));
  
  addCmdString(mdScout.setTextColors(C_RED,C_LTGRAY));
  addCmdString(mdScout.outTextR(410,320,"ds18:"));
  addCmdString(mdScout.outTextL(430,320,String(ds_t[0],1) + " °C"));
  addCmdString(mdScout.outTextL(430,370,String(ds_t[1],1) + " °C"));
  addCmdString(mdScout.setTextColors(C_YELLOW,C_LTGRAY));
  addCmdString(mdScout.outTextR(410,420,"DevPt:"));
  addCmdString(mdScout.outTextL(430,420,String(Tp,1) + " °C"));
// вывод времени в правом верхнем углу
  addCmdString(mdScout.setTextColors(C_YELLOW,C_BLUE));
  addCmdString(mdScout.setTextSize(30));
  addCmdString(mdScout.outTextL(170,90,dateToBuffer()));
  addCmdString(mdScout.outTextR(660,90,timeToBuffer()));
// определение кнопок для редактирования даты и времени
  addCmdString(mdScout.setTextColors(C_GREEN,C_BLUE));
  addCmdString(mdScout.setLineColors(C_YELLOW,C_RED));
  addCmdString(mdScout.defineTouchButton(250,470,101,111,"   Дата   "));
  addCmdString(mdScout.defineTouchButton(450,470,102,112,"  Время   "));
  addCmdString(mdScout.defineTouchButton(250,530,103,113,"  Файлы   "));

  sendString();
} // of showMain

void showRTCSetup(bool setupTime) {
  String curTime,curDate;
  byte i;
  curTime = timeToBuffer();
  curDate = dateToBuffer();
  addCmdString(mdScout.setDisplayColors(C_BLACK,C_LTGRAY));
  addCmdString(mdScout.setLineColors(3/*C_RED*/,C_RED));
  addCmdString(mdScout.fillDisplayFG());
  addCmdString(mdScout.drawRectangle(130,40,660,180));
//  addCmdString(mdScout.setLineColors(C_LTGRAY,C_MAGENTA));
//  addCmdString(mdScout.fillRectangleFG(50,20,350,60));
//  addCmdString(mdScout.fillRectangleBG(400,20,700,60));

/*  for(i=0;i<16;i++) {
    addCmdString(mdScout.setLineColors(i,(i+5)%16));
    addCmdString(mdScout.drawLine(130,200+i*5,660,200+i*20));
  } */

  addCmdString(mdScout.setTextSize(40));
  addCmdString(mdScout.setTextColors(C_BLUE,C_BLACK));
  if(setupTime) {
    addCmdString(mdScout.outTextR(450,80,"Текущее время: "));
    addCmdString(mdScout.outTextL(470,80,curTime));
    addCmdString(mdScout.outTextR(450,140,"Новое время: "));
    addCmdString(mdScout.setTextColors(C_YELLOW,C_BLUE));
    addCmdString(mdScout.defineEditText(1,470,110,120,40));
    addCmdString(mdScout.setEditTextStr(1,curTime));
  }
  else {
    addCmdString(mdScout.outTextR(430,80,"Текущая дата: "));
    addCmdString(mdScout.outTextL(450,80,curDate));
    addCmdString(mdScout.outTextR(430,140,"Новая дата: "));
    addCmdString(mdScout.setTextColors(C_YELLOW,C_BLUE));
    addCmdString(mdScout.defineEditText(2,450,110,160,40));
    addCmdString(mdScout.setEditTextStr(2,curDate));
  }

  sendString();
} // of showRTCSetup

void showFileSelector(int ID) {
  File root,f;
  int n=0,nMax=20;
  addCmdString(mdScout.setTextColors(C_BLUE,C_LTGRAY));
  addCmdString(mdScout.setTextSize(20));
  addCmdString(mdScout.defineListView(12, 10, 60, 250, 400));  // TL0
  root = SD.open("/");
  f = root.openNextFile();
  while(n<nMax) {
    if(f) {
      if(!f.isDirectory()) {
        addCmdString(mdScout.writeListView(12, f.name()));    
        n++;
      }
      f = root.openNextFile();
    }
    else n=nMax;
  }
}

void showFileContext(String &sCmd) {
  String line = "", fileName; //#FL12,4,4,202100112.MET
  char c;
  File f;
  int p;

  fileName = sCmd;
  //Serial.print(">>--------->>=========== ShowFile command: ");  Serial.println(fileName);
  p=fileName.indexOf(','); if(p>=0) fileName = fileName.substring(p+1); // первая запятая
  p=fileName.indexOf(','); if(p>=0) fileName = fileName.substring(p+1); // вторая запятая
  p=fileName.indexOf(','); if(p>=0) fileName = fileName.substring(p+1); // третья запятая
  p=fileName.indexOf(';'); if(p>=0) fileName = fileName.substring(0,p); // точка с запятой
  //Serial.print(">>--------->>=========== del semicolon: ");  Serial.println(fileName);
 
   if(fileName.length()>0) {
    Serial.println("file name: "+fileName);
    addCmdString(mdScout.writeTerminal(1, "-=<>=-"));
    f=SD.open(fileName);
    if (f) {
      // read from the file until there's nothing else in it:
      while (f.available()) {
        c = f.read();
        if(c==10 || c==13) {
          if(line.length()>0) {
            addCmdString(mdScout.writeTerminal(1, line));
            line = "";
          }
        }
        else line = line+c;
      }
      if(line.length()>0) addCmdString(mdScout.writeTerminal(1, line));

      // close the file:
      f.close();
    }
  }
}

void showFiles() {
  addCmdString(mdScout.setDisplayColors(C_BLACK,C_LTGRAY));
  addCmdString(mdScout.setLineColors(C_YELLOW,C_RED));
  addCmdString(mdScout.fillDisplayFG());
  addCmdString(mdScout.drawRectangle(130,40,700,210));
  addCmdString(mdScout.setTextSize(40));
  addCmdString(mdScout.setTextColors(C_CYAN,C_DKGRAY));  
  addCmdString(mdScout.outTextC(400,40,"Файлы метеостанции"));
  showFileSelector(1);
  addCmdString(mdScout.setTextSize(20));
  addCmdString(mdScout.defineTerminal(1, 270,  60, 760, 700));  // TW0
  sendString();
} // of showFiles

void interpreteCommand(String &sCmd,byte PortNum) {
  if(sCmd.length()>0) {
    Serial.print("Interprete command: ["); Serial.print(sCmd); Serial.println("]");
    if(sCmd.equals("redrw"))    showMain();     // на смартфоне была нажата кнопка "redrw"
    else if(sCmd.equals("drw")) showMain();     // на смартфоне была нажата кнопка "drw"
    else if(sCmd.equals("101")) showRTCSetup(false);
    else if(sCmd.equals("102")) showRTCSetup(true);
    else if(sCmd.equals("103")) showFiles();
    else if(sCmd.indexOf("#FL")>=0) {
      showFileContext(sCmd);
    }
    else if(sCmd.indexOf("#FE1")>=0) {
      setNewTime(sCmd);
      showMain();
    }
    else if(sCmd.indexOf("#FE2")>=0) {
      setNewDate(sCmd);
      showMain();
    }
    else if(sCmd.indexOf("settime")>=0) cmd_settime(sCmd); 
    else if(sCmd.indexOf("gettime")>=0) cmd_gettime(); 
    else if(sCmd.indexOf("setdate")>=0) cmd_setdate(sCmd);
    else if(sCmd.indexOf("getdate")>=0) cmd_getdate();
    else if(sCmd.equals("fun1"));
    // ...
    else if(sCmd.equals("fun6"));
    sCmd = "";
  }
} // of interpreteCommand

Получаемые с датчиков BME280 и DS18B20 значения теперь не только выводятся на экран смартфона, но и сохраняются в журналах на SD-карточке. Каждому дню соответствует отдельный файл, его имя указывает на дату журнала. Формат имени файла - "YYYYMMDD.MET",  где YYYY - это год, MM - месяц и DD - день. В текущей настройке запись данных в журнал производится каждые десять минут (константа LOG_PERIOD).

Эти файлы можно скопировать к себе на компьютер, а можно и просмотреть со смартфона. Для этой цели на главном экране MoDyz появилась новая кнопка - "Файлы". При нажатии на нее открывается экран "Файлы метеостанции", на котором слева выводится список всех имеющихся на карточке файлов. Тапнув на любую из строк этого списка, можно открыть соответствующий файл - его содержимое выводится в правом окне.

  

До этого MoDyz не поддерживал работу со списками выбора, поэтому пришлось разработать соответствующий класс и выпустить новую версию - 1.2.9. Загрузить ее можно здесь.

Появление нового элемента управления в MoDyz потянуло за собой и программирование новых функций в MD_Scout:
defineListView - определение элемента "список выбора"
writeListView - загрузка очередной строки в список выбора

Новая версия MD_Scout также доступна по вышеприведенной ссылке (загружайте последнюю версию пакета от 28.01.21)