Вендор: СУБД Caché, OLAP DeepSee, шина Ensemble
142,34
рейтинг
15 февраля в 16:30

Разработка → Метеостанция на Arduino с визуализацией данных tutorial

Paul Klee In The Style Of Kairouan

Введение


Про метеостанции на Arduino писали и не раз. В своё оправдание скажу, что был хакатон — а нашей команде (в составе меня и хабраюзера ViArt) хотелось попробовать работу с Arduino. Кроме того к нашей метеостанции прикручена визуализация данных. Если хотите узнать, какая база данных может получать данные по com-порту без промежуточных звеньев в виде web-сервера, файлов или ещё каких-то ухищрений, добро пожаловать под кат.

Работа с устройствами


В InterSystems Caché можно работать напрямую с большим количеством физических и логических типов устройств. Вот они:

  • Диски
  • Магнитные ленты
  • Файлы
  • Терминалы
  • TCP порты
  • COM порты
  • и другие

Работа происходит в 5 этапов:

  1. Сначала с помощью команды OPEN регистрируется тот факт что текущий процесс получает доступ к устройству
  2. Затем, когда нужно работать с устройством, оно делается текущим с помощью команды USE
  3. Работа с устройством. Получение данных с устройства командой READ, отправка данных на устройство командой WRITE
  4. Переключение на другое устройство ввода-вывода (опять USE)
  5. Закрытие устройства командой CLOSE

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

Мигаем лампочкой из Caché


Итак, соберём на Arduino схему, которая читает данные из COM порта и включает светодиод на указанное число миллисекунд.

Схема

Код на C
/*  Led.ino
 *
 *  Пример получения данных по COM порту
 *  Подключите светодиод к выводу ledPin 
 *
 */

// Вывод светодиода (цифровой)
#define ledpin 8

// "Буфер" поступающих данных
String inString = "";

void setup() {
	Serial.begin(9600);
	pinMode(ledpin, OUTPUT);
	digitalWrite(ledpin, LOW);
}

void loop() {
	// Получаем данные из COM порта
	while (Serial.available() > 0) {
	int inChar = Serial.read();
	if (isDigit(inChar)) {
		// Получаем 1 символ, 
		// Прибавляем его к строке
		inString += (char)inChar;
	}

	// Доходим до новой строки
	if (inChar == '\n') {
		// Включаем светодиод
		digitalWrite(ledpin, HIGH);
		int time = inString.toInt();
		delay(time);
		digitalWrite(ledpin, LOW);
		// Обнуляем полученную строку
		inString = "";
	}
  }

} 


В Caché напишем метод, подключающийся к com порту и отправляющий строку 1000\n:

/// Отправляем на порт строку 1000\n
ClassMethod SendSerial()
{
	set port = "COM1"
	open port:(:::" 0801n0":/BAUD=9600)     // Открываем устройство
	set old = $IO // Записываем текущее устройство ввода-вывода
	use port  // Переключаемся на com порт
	write $Char(10) // Отправка пробного пакета данных
	hang 1
	write 1000 _ $Char(10) // Передаём строку 1000\n
	use old // Переключаем вывод на терминал
	close port // Закрываем устройство
}

Строка «0801n0» это список параметров подключения к Com порту, подробно расписана в документации. А /BAUD=9600 — это скорость подключения в бодах.

В результате, если вызвать этот метод в терминале:

do ##class(Arduino.Habr).SendSerial()

То он ничего не выведет, а вот светодиод загорится на секунду.

Получаем данные в Caché


Теперь подключим клавиатуру (keypad) к Caché и будем передавать данные. Это может быть использовано как, например, дополнительная аутентификация пользователя с помощью делегации авторизации и рутины ZAUTHENTICATE.mac

Схема

Код на C
/*  Keypadtest.ino
 *
 *  Пример использования библиотеки Keypad
 *  Подключите Keypad к выводам Arduino указанным в
 *  rowPins[] and colPins[].
 *
 */

// Репозиторий библиотеки:
// https://github.com/Chris--A/Keypad
#include <Keypad.h>

const byte ROWS = 4; // Четыре строки
const byte COLS = 4; // Три столбцы
// Карта соответствия кнопок и символов
char keys[ROWS][COLS] = {
	{'1','2','3','A'},
	{'4','5','6','B'},
	{'7','8','9','C'},
	{'*','0','#','D'}
};
// Подключите разьёмы keypad 1-8 (сверху-вниз) к Arduino разьёмам 11-4. 1->11, 2->10, ... , 8->4 
// Подключите keypad ROW0, ROW1, ROW2 и ROW3 к этим выводам Arduino
byte rowPins[ROWS] = { 7, 6, 5, 4 };
// Подключите keypad COL0, COL1 and COL2 к этим выводам Arduino
byte colPins[COLS] = { 8, 9, 10, 11 }; 

// Инициализация Keypad
Keypad kpd = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

#define ledpin 13

void setup()
{
	Serial.begin(9600); 
}

void loop()
{
	char key = kpd.getKey(); // Поолучаем нажатую кнопку
	if(key)
	{
		switch (key)
	{
		case '#':
			Serial.println();
		default:
			Serial.print(key);
	}
	}
}


В Caché напишем метод, подключающийся к com порту и получающий строку данных:

/// Получение одной строки данных (до конца строки)
ClassMethod ReceiveOneLine() As %String
{
	port = "COM1"
	set str=""
	try {
		open port:(:::" 0801n0":/BAUD=9600)
		set old = $io // Запоминаем текущее устройство ввода-вывода
		use port
		read str // Читаем, пока не встретим символ конца строки
		use old
		close port
	} catch ex {
		close port
	}
	return str
}

В терминале выполним:

write ##class(Arduino.Habr).ReceiveOneLine()

И он перейдёт в режим ожидания, пока на keypad не нажмём "#" (по нажатию на который будет передан конец строки), после чего в терминале будет выведена введённая строка.

Итак, это были основы взаимодействия с устройством, теперь перейдём к метеостанции.

Метеостанция


Для сборки метеостанции использовали фоторезистор и датчик DHT11 (температура и влажность). Схема подключения:



Код на C
/* Meteo.ino
 *
 * Программа, регистрирующая влажность, температуру и яркость
 * Отправляет результаты на COM port
 * Формат вывода: H=1.0;T=1.0;LL=1;
 */

//Пин фоторезистора (аналоговый)
int lightPin = 0;

// Пин DHT-11 (цифровой)
int DHpin = 8; 

// Массив, хранящий данные DHT-11
byte dat[5]; 

// Первоначальная настройка
void setup()
{
	Serial.begin(9600); 
	pinMode(DHpin,OUTPUT); 
}

 /*
 * Выполняется после setup()
 * Основной бесконечный цикл
 */
void loop()
{
	delay(1000); // Замер примерно 1 раз в секунду
	int lightLevel = analogRead(lightPin); //Получаем уровень освещённости 

	temp_hum(); // Получаем температуру и влажность в переменную dat
	// И выводим результат
	Serial.print("H="); 
	Serial.print(dat[0], DEC);   
	Serial.print('.'); 
	Serial.print(dat[1],DEC);	
	Serial.print(";T="); 
	Serial.print(dat[2], DEC);	
	Serial.print('.'); 
	Serial.print(dat[3],DEC);	 
	Serial.print(";LL="); 
	Serial.print(lightLevel);
	Serial.println(";");
}

// Получить данные от DHT-11 в dat
void temp_hum() 
{ 
	digitalWrite(DHpin,LOW);
	delay(30);  
	digitalWrite(DHpin,HIGH); 
	delayMicroseconds(40);
	pinMode(DHpin,INPUT);
	while(digitalRead(DHpin) == HIGH);
	delayMicroseconds(80);
	if(digitalRead(DHpin) == LOW); 
	delayMicroseconds(80);
	for(int i=0;i<4;i++)
	{
	  dat[i] = read_data();
	}
	pinMode(DHpin,OUTPUT);
	digitalWrite(DHpin,HIGH);
} 

// Получить часть данных от DHT-11
byte read_data() 
{
	byte data; 
	for(int i=0; i<8; i++) 
	{ 
		if(digitalRead(DHpin) == LOW) 
		{ 
			while(digitalRead(DHpin) == LOW); 
			delayMicroseconds(30);
			if(digitalRead(DHpin) == HIGH) 
			{
				data |= (1<<(7-i));
			}
			while(digitalRead(DHpin) == HIGH); 
		}
	} 
	return data; 
} 

После загрузки кода на Arduino она начинает посылать данные на COM порт в следующем формате:

H=34.0;T=24.0;LL=605;

Где:

  • H — влажность (от 0 до 100 процентов)
  • T — температура в градусах Цельсия
  • LL — освещённость (от 0 до 1023)

Надо как-то его хранить в Caché. Для этого напишем хранимый класс Arduino.Info:

Arduino.Info
Class Arduino.Info Extends %Persistent
{

Parameter SerialPort As %String = "com1";

Property DateTime As %DateTime;

Property Temperature As %Double;

Property Humidity As %Double(MAXVAL = 100, MINVAL = 0);

Property Brightness As %Double(MAXVAL = 100, MINVAL = 0);

Property Volume As %Double(MAXVAL = 100, MINVAL = 0);

ClassMethod AddNew(Temperature = 0, Humidity = 0, Brightness = 0, Volume = 0)
{
	set obj = ..%New()
	set obj.DateTime=$ZDT($H,3,1)
	set obj.Temperature=Temperature
	set obj.Humidity=Humidity
	set obj.Brightness=Brightness/1023*100
	set obj.Volume=Volume
	write $SYSTEM.Status.DisplayError(obj.%Save())
}

И добавим туда метод, который будет принимать данные в формате Arduino, и преобразовывать их объекты класса Arduino.Info:

/// Получаем поток данных в формате H=34.0;T=24.0;LL=605;\n 
/// И преобразуем их в объекты класса Arduino.Info
ClassMethod ReceiveSerial(port = {..#SerialPort})
{
	try {
		open port:(:::" 0801n0":/BAUD=9600)
		set old = $IO
		use port
		for {
			read x //Читаем одну строку
			set Humidity = $Piece($Piece(x,";",1),"=",2)
			set Temperature =  $Piece($Piece(x,";",2),"=",2)
			set Brightness =  $Piece($Piece(x,";",3),"=",2)
			if (x '= "") {
				do ..AddNew(Temperature,Humidity,Brightness) // Добавляем данные
			}
		}
	} catch anyError {
		close port
	}
}

После этого нам нужно запустить Arduino и выполнить в терминале метод ReceiveSerial:

write ##class(Arduino.Info).ReceiveSerial()

Этот метод в бесконечном цикле будет собирать и сохранять данные, приходящие от Arduino.

Визуализация данных


После того, как метеостанция была собрана, наша команда запустила её на улице (была уже ночь) и оставила собирать данные до утра.



К утру данные накопились (~36000 записей) и мы визуализировали их в BI DeepSee, вот что получилось.

График яркости. Явно виден рассвет в районе 5:50:



Графики температуры и влажности.



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

Демо


Доступно тут.

Выводы


InterSystems Caché позволяет организовать взаимодействие с большим числом устройств напрямую. Возможна быстрая разработка решений по сбору и визуализации данных.

Ссылки


» Документация
» Репозитоий с кодом
Автор: @eduard93
InterSystems
рейтинг 142,34
Вендор: СУБД Caché, OLAP DeepSee, шина Ensemble

Комментарии (20)

  • +2
    разве Dht11 предназначен для улицы?

    image
    • +1
      Дело было ранней осенью и темепература не падала ниже +10.
      • +1
        Это я заметил на графике. Просто даже dht21/22 ниже нуля врёт безбожно.
        Для температуры лучше далассовский датчик использовать.
        • +1
          DS18B20 влажность не меряет =) DHT11 врет безбожно и не показывает температуру ниже нул
          • +1
            Прекрасная штука для температуры — BMP-180 (или 280). Выдает температуру/давление, подключается по I2C. У меня отлично работает за окном в течение года в связке с NetDuino и облачным бэкендом в Microsoft Azure: http://blog.soshnikov.com/2015/01/26/
            • +1
              да у меня тоже такой стоит для давления. но температуру беру с DS-ки
              • +1
                А чем DS-ка лучше, чем BMP? Мне казалось, они оба показывают примерно с одинаковой точностью до 0,1 градуса...
                • +1
                  DS-ка бывает в waterproof исполнении
                • +1
                  Для outdoor измерения — самое-то.
                  А давление практически одинаковое внутри и снаружи, если измерять на одной высоте.
          • +1
            имелгось ввиду DS-ом мерить температуру, а DHT-шкой (21/22) влажность.
            DHT11 ниже нуля не то чтобы врёт, а не работает.
            • +1
              Я так и написал. Но он неточен даже в своем обозначенном рабочем диапазоне температур. Я собирал тестовый стенд с тремя датчиками: DHT11, DHT22 и DS18B20. Последние имели разброс показаний около одной-трех десятых, первый же показывал температуру почти на два градуса выше.

              Влажность же у моего DHT11 вообще была константным значением 31-33% =)
              • +1
                я как-то покупал на ali 6 ds-ок, один из 6-ти датчиков глючил, остальные пять были исключительно точными .
  • +1
    Когда-нибудь до этого доберусь))
  • +2
    К утру данные накопились (~36000 записей) и мы визуализировали их в BI DeepSee, вот что получилось.

    Я бы еще отметил, что для визуализации помимо базового DeepSee использовалась вот эта связочка: REST API MDX2JSON и DeepSee Web — Angular клиент для визуализации MDX данных из DeepSee.
  • +1
    Что похожее тоже сделали в рамках hackday. Демо проекта- www.youtube.com/watch?v=8Z5C1xoavaA
  • +1
    а как бы ещё все это заставить писать на SD карту, а так же запилить веб сервер, складывать данные в базу и публикацияю\мониторинг данных через веб морду?
    • +1
      https://meteo.igoninlab.com/
      пилю на досуге, железо — stm32f103 + esp8266, сервер — nodejs
    • +2
      Так, за исключением записи на SD, это и было сделано. Запись сразу в базу с визуализацией в web через deepsee for web (смотри коммент выше)
      • +2
        В DeepSee можно включить автообновление графиков (а точнее OLAP куба), вот статья. Т.е. Arduino постоянно отсылает данные, а раз в N минут куб перестраивается и пользователь видит обновлённые графики.
  • +1
    Ну или можно было взять Blynk и сделать все за 10 мин.

    Красивости
    image

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка