Метеостанция на 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 порта и включает светодиод на указанное число миллисекунд.

Схема
aded6495459b49a4a632d36001f7b5ce.jpg


Код на 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

Схема
c19196d9b9b64d51974b87c1e94a7650.png


Код на C
/*  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 (температура и влажность). Схема подключения:

d5331b42f82c4962948fd95856174dc5.png

Код на 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.

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


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

ffe158d1975943c6a8bbce8c468a1c48.jpg

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

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

8480edaf2ef44ec8b2cab1b57318ec3d.PNG

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

f1450b5939e04f25b4afb0ee4413c563.PNG

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

Демо


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

Выводы


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

Ссылки


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

© Habrahabr.ru