Мониторинг среды в реальном времени: Arduino MKR1000 + Environment Click (BME680) и WolkAbout IoT
Компоненты и материалы
| Компонент | Количество |
|---|---|
| Arduino MKR1000 | 1 |
| MikroE Environment Click (BME680: температурв, влажность, давление, газ, высота) | 1 |
Инструменты
- Перемычки «мама-мама» (Adafruit)
Программное обеспечение и сервисы
- Arduino IDE
- WolkAbout IoT Platform (облачная платформа для визуализации и правил)
О проекте
Подключаем Environment Click к Arduino MKR1000 по I2C (пины 11 — SDA, 12 — SCL, VCC, GND) и отправляем данные на WolkAbout IoT Platform. Замер каждую минуту, публикация каждые 15 минут. При ошибке связи данные буферизируются во Flash-памяти (макс. 96 записей/сутки, флеш выдерживает 10 000 циклов).
Необходимые библиотеки (Library Manager или GitHub)
- WiFi101
- Adafruit Unified Sensor Driver
- Adafruit BME680 Library
- RTCZero
- FlashStorage
- WolkConnect (Arduino-коннектор к WolkAbout)
Настройка скетча
- Введите WiFi-креды (строки 33–34):
const char* ssid = "<YOUR SSID>"; const char* wifi_pass = "<YOUR WIFI PASSWORD>"; - Создайте Device Type на WolkAbout: загрузите
Environment click-deviceType.json(приложен) в Device Management → Device Type Management → + Upload. - Создайте Device из этого типа: имя, Connectivity Type = MQTT, галка «Create Default Semantic».
- Скопируйте
device_keyиdevice_passwordв скетч:const char *device_key = "device_key"; const char *device_password = "device_password"; - Загрузите скетч в плату.
Дашборд на WolkAbout
- Visualisation and Monitoring → + Add dashboard → имя, scope = имя устройства.
- Add widget → Trending → выберите Temperature, Pressure, Humidity по очереди.
- Add widget → Reading → Altitude.
- Add widget → Card → Gas Resistance.
- Разместите виджеты как удобно.
Теперь разверните устройство в нужной среде и следите за условиями удалённо. Движок правил WolkAbout позволяет получать уведомления при выходе значений за пороги.
Программный код (Environment monitoring sketch)
#include <Adafruit_Sensor.h>
#include <Adafruit_BME680.h>
#include <bme680_defs.h>
#include <bme680.h>
#include <WiFi101.h>
#include <RTCZero.h>
#include <FlashStorage.h>
#include "WolkConn.h"
#include "MQTTClient.h"
#define STORAGE_SIZE 32
#define SEALEVELPRESSURE_HPA (1013.25)
typedef struct {
boolean valid;
outbound_message_t outbound_messages[STORAGE_SIZE];
uint32_t head;
uint32_t tail;
boolean empty;
boolean full;
} Messages;
static Messages data;
const char* ssid = "ssid";
const char* wifi_pass = "wifi_pass";
const char *device_key = "device_key";
const char *device_password = "device_password";
const char* hostname = "api-demo.wolkabout.com";
int portno = 1883;
WiFiClient espClient;
PubSubClient client(espClient);
static wolk_ctx_t wolk;
FlashStorage(flash_store, Messages);
Adafruit_BME680 bme;
RTCZero rtc;
bool read = false;
const byte readEvery = 1; // каждую минуту
bool publish = false;
const byte publishEvery = 10; // каждые 10 минут
byte publishMin;
// --- Flash circular buffer ---
void _flash_store() { data.valid = true; flash_store.write(data); }
void increase_pointer(uint32_t* p) { if(*p == STORAGE_SIZE-1) *p=0; else (*p)++; }
void _init() {
data = flash_store.read();
if(!data.valid) { data.head=0; data.tail=0; data.empty=true; data.full=false; }
}
bool _push(outbound_message_t* m) {
if(data.full) increase_pointer(&data.head);
memcpy(&data.outbound_messages[data.tail], m, sizeof(outbound_message_t));
increase_pointer(&data.tail);
data.empty=false; data.full=(data.tail==data.head); return true;
}
bool _peek(outbound_message_t* m) { memcpy(m, &data.outbound_messages[data.head], sizeof(outbound_message_t)); return true; }
bool _pop(outbound_message_t* m) {
memcpy(m, &data.outbound_messages[data.head], sizeof(outbound_message_t));
increase_pointer(&data.head); data.full=false; data.empty=(data.tail==data.head); return true;
}
bool _is_empty() { return data.empty; }
// --- WiFi & Wolk ---
void init_wifi() {
if(WiFi.status()!=WL_CONNECTED) while(WiFi.begin(ssid,wifi_pass)!=WL_CONNECTED) delay(1000);
}
void setup_wifi() {
delay(10);
if(WiFi.status()!=WL_CONNECTED) {
int n=0; while(WiFi.begin(ssid,wifi_pass)!=WL_CONNECTED) { if(++n==10){Serial.println("Couldn't reach WiFi!");break;} delay(1000); }
}
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW);
_init(); init_wifi();
wolk_init(&wolk, NULL,NULL,NULL,NULL, device_key,device_password, &client, hostname,portno, PROTOCOL_JSON_SINGLE, NULL,NULL);
wolk_init_custom_persistence(&wolk, _push,_peek,_pop,_is_empty);
if(!bme.begin()) digitalWrite(LED_BUILTIN, HIGH);
bme.setTemperatureOversampling(BME680_OS_8X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_4X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320,150); delay(200);
read=true; publish=true;
wolk_connect(&wolk); delay(100);
wolk_update_epoch(&wolk);
while(!wolk.pong_received) { wolk_process(&wolk); digitalWrite(LED_BUILTIN,HIGH); delay(1000); }
digitalWrite(LED_BUILTIN,LOW);
wolk_disconnect(&wolk);
rtc.begin(); rtc.setEpoch(wolk.epoch_time);
rtc.setAlarmTime(rtc.getHours(), (rtc.getMinutes()+readEvery)%60, rtc.getSeconds());
rtc.enableAlarm(rtc.MATCH_MMSS);
rtc.attachInterrupt(alarmMatch);
publishMin = (rtc.getMinutes()+publishEvery)%60;
WiFi.lowPowerMode();
}
void loop() {
if(read) {
read=false;
if(!bme.performReading()) digitalWrite(LED_BUILTIN,HIGH);
wolk_add_numeric_sensor_reading(&wolk, "T", bme.temperature, rtc.getEpoch());
wolk_add_numeric_sensor_reading(&wolk, "H", bme.humidity, rtc.getEpoch());
wolk_add_numeric_sensor_reading(&wolk, "P", bme.pressure/100.0, rtc.getEpoch());
wolk_add_numeric_sensor_reading(&wolk, "GR", bme.gas_resistance, rtc.getEpoch());
wolk_add_numeric_sensor_reading(&wolk, "A", bme.readAltitude(SEALEVELPRESSURE_HPA), rtc.getEpoch());
int alarmMin = (rtc.getMinutes()+readEvery)%60;
rtc.setAlarmMinutes(alarmMin); delay(100);
}
if(publish) {
publish=false; setup_wifi(); wolk_connect(&wolk);
if(!wolk.is_connected) _flash_store();
delay(100);
if(wolk_publish(&wolk)==W_TRUE) _flash_store();
publishMin = (rtc.getMinutes()+publishEvery)%60; delay(100);
wolk_disconnect(&wolk); delay(100);
}
delay(100);
}
void alarmMatch() {
read=true;
if(publishMin == rtc.getMinutes()) publish=true;
}
Device Type JSON (Environment click-deviceType.json)
[
{
"name": "Environment click",
"description": "Device type for the BME680 sensor on MikroElektronika's board Environment Click",
"guid": "def68ff6-63c5-4f9c-a066-bcc944b7bd2d",
"deviceType": "STANDARD",
"protocol": "JSON",
"connectivityType": null,
"firmwareUpdateType": null,
"feeds": [
{ "name": "Temperature", "reference": "T", "unit": { "name": "CELSIUS", "guid": "7c805d10-a879-11e9-83cd-0a0027000005" } },
{ "name": "Humidity", "reference": "H", "unit": { "name": "HUMIDITY_PERCENT", "guid": "7c8080a3-a879-11e9-83cd-0a0027000005" } },
{ "name": "Pressure", "reference": "P", "unit": { "name": "MILLIBAR", "guid": "7c807980-a879-11e9-83cd-0a0027000005" } },
{ "name": "Gas Resistance", "reference": "GR", "unit": { "name": "OHM", "guid": "7c805890-a879-11e9-83cd-0a0027000005" } },
{ "name": "Altitude", "reference": "A", "unit": { "name": "METRE", "guid": "7c805a59-a879-11e9-83cd-0a0027000005" } }
],
"actuators": [], "alarms": [], "configs": [],
"attributes": [
{ "name": "CONNECTIVITY_TYPE", "dataType": "ENUM", "validationRegex": "^(MQTT|HTTP)$", "options": ["MQTT","HTTP"], "defaultValue": "MQTT", "required": true, "readOnly": true },
{ "name": "FIRMWARE_UPDATE_ENABLED", "dataType": "BOOLEAN", "validationRegex": "^(true|false)$", "options": null, "defaultValue": null, "required": false, "readOnly": true },
{ "name": "FIRMWARE_VERSION", "dataType": "STRING", "validationRegex": null, "options": null, "defaultValue": null, "required": false, "readOnly": true },
{ "name": "FILE_DIRECT_DOWNLOAD_ENABLED", "dataType": "BOOLEAN", "validationRegex": "^(true|false)$", "options": null, "defaultValue": null, "required": false, "readOnly": true },
{ "name": "FILE_URL_DOWNLOAD_ENABLED", "dataType": "BOOLEAN", "validationRegex": "^(true|false)$", "options": null, "defaultValue": null, "required": false, "readOnly": true }
]
}
]
Полезные ссылки
- Репозиторий с Arduino-скетчем
- WiFi101 library
- Adafruit Unified Sensor
- Adafruit BME680 Library
- RTCZero library
- FlashStorage library
- WolkConnect-Arduino library
Схема подключения
Environment Click подключается к пинам I2C (11, 12), VCC и GND Arduino MKR1000. Схема в разделе Schematics.
Производственный процесс
- Сборка Bluetooth-робота-шпиона на Arduino с управлением с телефона
- Датчик CO2 K30: мониторинг качества воздуха в реальном времени на Arduino
- FlickMote: жестовое IR-пульт для умного дома на Arduino 101 и ультразвуковом датчике
- DIY Arduino TV-B-Gone: универсальный пульт для выключения любого телевизора
- Создание пользовательских LED-часов с будильником — видно с 12 метров
- Find Me: Умный поисковик вещей на Arduino и Bluetooth
- Оптимизированные решения питания для проектов на Arduino
- Крестики-нолики на Arduino с матрицей MAX7219 и картонным корпусом
- Пошаговое руководство: сборка умного четырёхногого робота на Arduino
- Бесконтактный термометрический шлюз для быстрого и точного скрининга