Метеостанция на Arduino с визуализацией данных

от автора

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é позволяет организовать взаимодействие с большим числом устройств напрямую. Возможна быстрая разработка решений по сбору и визуализации данных.

Ссылки

» Документация
» Репозитоий с кодом

ссылка на оригинал статьи https://habrahabr.ru/post/273749/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *