Метеостанция на Arduino с визуализацией данных
Введение
Про метеостанции на Arduino писали и не раз. В своё оправдание скажу, что был хакатон —, а нашей команде (в составе меня и хабраюзера ViArt) хотелось попробовать работу с Arduino. Кроме того к нашей метеостанции прикручена визуализация данных. Если хотите узнать, какая база данных может получать данные по com-порту без промежуточных звеньев в виде web-сервера, файлов или ещё каких-то ухищрений, добро пожаловать под кат.
Работа с устройствами
В InterSystems Caché можно работать напрямую с большим количеством физических и логических типов устройств. Вот они:
- Диски
- Магнитные ленты
- Файлы
- Терминалы
- TCP порты
- COM порты
- и другие
Работа происходит в 5 этапов:
- Сначала с помощью команды OPEN регистрируется тот факт что текущий процесс получает доступ к устройству
- Затем, когда нужно работать с устройством, оно делается текущим с помощью команды USE
- Работа с устройством. Получение данных с устройства командой READ, отправка данных на устройство командой WRITE
- Переключение на другое устройство ввода-вывода (опять USE)
- Закрытие устройства командой CLOSE
Как это выглядит на практике?
Мигаем лампочкой из Caché
Итак, соберём на Arduino схему, которая читает данные из COM порта и включает светодиод на указанное число миллисекунд.
/* 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
/* Keypadtest.ino
*
* Пример использования библиотеки Keypad
* Подключите Keypad к выводам Arduino указанным в
* rowPins[] and colPins[].
*
*/
// Репозиторий библиотеки:
// https://github.com/Chris--A/Keypad
#include
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 (температура и влажность). Схема подключения:
/* 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:
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é позволяет организовать взаимодействие с большим числом устройств напрямую. Возможна быстрая разработка решений по сбору и визуализации данных.
Ссылки
» Документация
» Репозитоий с кодом