Pull to refresh
0
InterSystems
InterSystems IRIS: СУБД, ESB, BI, Healthcare

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

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

Ссылки


» Документация
» Репозитоий с кодом
Tags:
Hubs:
+16
Comments 20
Comments Comments 20

Articles

Information

Website
www.intersystems.com
Registered
Founded
1978
Employees
1,001–5,000 employees
Location
США