Дачная метеостанция
- Войдите на сайт для отправки комментариев
Весной этого года у меня на даче появилась метеостанция, следящая за температурой, давлением и влажностью воздуха на участке. В ее состав входит модуль 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. Как это сделать, будет сообщено в продолжении...
Я себе на улицу летом поставил BME280, пока полёт нормальный. А раньше ставил DHT22, а они через несколько месяцев начинали "залипать" по влажности к верхнему пределу, приходилось менять.
step962
Добрый день.
а, можно схему увидеть ? и по подробней про NodeMCU
интересный выбор железа, а почему не ESP32 к примеру
AndreyD:
У меня в первом (запущенном весной) варианте модули BMP180 и DHT22 расположены под крышей бани (на мансарде), а метровый поводок термодатчика DS18B20 выведен наружу с северной стороны, где он постоянно находится в тени и не нагревается солнцем. В новом варианте связка BMP180+DHT22 будет заменена на BME280 и появится место под два термодатчика DS18B20. По опыту эксплуатации первого варианта на термометр BMP180/BME280 я бы не полагался - он стабильно дает превышение в несколько десятых градуса - видимо, есть-таки пусть и небольшой, но нагрев от микроконтроллера.
ua6em:
Ну, то что в коробочке валялось. BluePill был выбран в качестве основы за наличие на его борту часов, а esp8266 (в виде NodeMCU) - в качестве связующего звена между основным микроконтроллером и USB-модемом, который умел общаться с окрестностями только по вайфаю. А до ESP32 у меня руки пока не дошли.
В коробочке не нашел ;)
Igor_116
Схему увидеть можно, только от картинки пользы никакой - создана она в Eagle и все сигналы выведены в шину. Так что какой проводок откуда и куда идет, понять невозможно:
Вот здесь можно загрузить проект (ссылка "Eagle-проект метеостанции") и исследовать связи средствами "Орла".
Там же загружается и библиотека MD_Scout, необходимая для компиляции скетча из первого сообщения.
Там же, тапнув по соответствующей ссылке в браузере смартфона, можно установить приложение MoDyz для тестирования общения с метеостанцией.
У меня вопрос: в чем заключается проблема существования отклонения на несколько десятых градуса при нахождении измерителя на улице либо в помещении?
step962
ЧТО МЕШАЕТ СЕМУ ВЫЛОЖИТЬ В ФОРМАТЕ Eagle
step962
ЧТО МЕШАЕТ СЕМУ ВЫЛОЖИТЬ В ФОРМАТЕ Eagle
Да я уже написал выше: бессмысленность этого действия. Вот (кликабельно):
а если имеется ввиду файл *.sch, то опять-же, выше была приведена ссылка на страницу, с которой можно скачать весь Eagle-проект - там и *.sch есть, и *.brd
Я полагаю, в близости "печки", то есть микроконтроллера. "Измеритель в помещении" - это модули bmp180 и dht22, расположенные на той же плате, что и Blue Pill. "Измеритель на улице" - это датчик, отнесенный от основной платы на длину провода, т.е. около 1 м.
На новом варианте метеостанции, там, где у меня на плате только bme280, а на метровых проводах два термометра ds18b20, я баловался с различным положением этих термометров. Туго привязанные друг к другу резинкой, они давали отклонение в измерениях не более 0,2 градуса. Иногда... Чаще это было отклонение 0,0. То есть даже меньше погрешности
При этом показания были стабильно меньше, чем с термометра, входящего в состав модуля bme280, который расположен в паре сантиметров от микроконтроллера.
Как-то так...
Вот, нашёл статейку, которую я читал перед покупкой BME280.
https://vladikoms.livejournal.com/97796.html
Два я в доме поставил, один под деревянным навесом во дворе. Точность мне не сильно была нужна, хотя бы примерно знать сколько там "за бортом".
step962
если имеется ввиду файл *.sch, то опять-же, выше была приведена ссылка на страницу, с которой можно скачать весь Eagle-проект - там и *.sch есть, и *.brd
из дома ссылка почему-то не открывается. сейчас на работе открыл
То, что BME/BMP "наловит" температуру от МК или DC-DC - это понятно, он так же ее покажет "неправильно" и на сквозняке.
Я не понимаю чем это мешает в жизни - увидеть вместо 23 градусов 23.4, к примеру. Это же не су-вид, не камера для выращивания разумной плесени.
Кстати, то, что DS-ки показывают одинаковую температуру не говорит о том, что эта температура истинна. Просто отклонения удачно совпадают.
У меня BMP280, МК и DC-DC (+ещё перефирия) находятся в корпусе щитка для электрических автоматов. DC-DC в верхней части, МК посередине, BMP в нижней. По 10 см от каждого от источников тепла. Конвекцией все тепловые "наводки"выносятся вверх, поэтому датчик температуру особо не искажает. А ловить десятые градуса при оценке состояния окружающей среды таким измерителем - это бессмыслица.
Перед тем как перейти к представлению следующего шага в развитии приложения, размещу здесь скриншот с экрана планшета, относящийся к стартовому варианту приложения (далее ШАГ_0):
ШАГ 1:
Перерыв между шагами 0 и 1 оказался длиннее, чем я предполагал: сначала во время тестовых прогонов выяснилось, что в MoDyz при перерисовке экранов происходило создание все новых экземпляров элементов управления, создаваемых по командам из Arduino-скетча. К каким-то катастрофическим последствиям это не приводило, приложение корректно отрабатывало все прикосновения к этим элементам. Только после перехода к генерации нескольких экранов (а на шаге 1 их стало три - стартовый экран, экран редактирования даты и экран редактирования времени) стало очевидным, что простое стирание изображения удаляет только графику, но не кнопки, поля ввода и прочие элементы управления из инструментария Java.
За недельку от упомянутого эффекта удалось избавиться, но тут и череда новогодних праздников подоспела. На этой неделе вроде бы стало полегче и намеченное для шага 1 стало обретать вполне реальные формы...
Что нового появилось на этом этапе?
На стартовом экране появились две кнопки, предназначенные для вызова экранов настройки даты и времени. Соответственно, добавилось два новых очень простеньких экрана - один для настройки даты, другой для настройки времени. Эти новшества относятся не столько к MoDyz, сколько к скетчу Arduino: программные коды на стороне Андроида не претерпели изменений, все соответствующие функции активируются из скетча Arduino.
Вот так выглядит обновленный стартовый экран (на нем появились поля для отображения даты и времени и две кнопки для вызова экранов настройки):
и два экрана настройки (по три поля вывода текста на каждом и по одному полю ввода):
Загрузить приложение для Андроида можно здесь.
Скетч пополнился функциями работы с часами, встроенными в микроконтроллер 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Шаг 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)