Сегодня мы рассматриваем комплект монитора качества воздуха “AirGradient ONE” – обновленную версию предыдущего монитора AirGradient . Устройство оснащено сенсорами от Sensirion и Plantower, что позволяет измерять множество параметров качества воздуха, включая CO2, PM2.5, TVOC, NOx, температуру и влажность. Это монитор качества воздуха для помещений с открытым исходным кодом и открытой аппаратной частью. Это означает, что Arduino-код, схемы, PCB и 3D-модели корпуса доступны разработчикам.
Распаковка DIY-комплекта AirGradient ONE
Устройство было упаковано в картонную коробку с приветственной карточкой от производителя. На карточке размещен QR-код, ведущий на веб-страницу с инструкцией по установке .
Поскольку был выбран комплект для самостоятельной сборки (есть и полностью собранная версия), некоторые сенсоры не были установлены заранее и были аккуратно упакованы в отдельные пакеты. Основная плата уже находилась внутри корпуса, но винты не были закручены. Дополнительные компоненты в коробке включали отвертку, винты, кабель USB Type-C и адаптер 5V 2A.
Технические характеристики
Этот комплект представляет собой AirGradient ONE на плате V9 с микроконтроллером ESP32-C3 Mini в качестве основного чипа. Также в комплекте идет OLED-дисплей размером 1,3 дюйма с разрешением 128×64 пикселей. На основной плате установлены 11 программируемых RGB-светодиодов NeoPixel для визуализации уровня качества воздуха. Подробный список компонентов приведен ниже.
- 1x Сенсор PM Plantower PMS5003
- 1x Сенсор CO2 Senseair S8
- 1x Модуль температуры и влажности SHT4x
- 1x Модуль сенсора TVOC/NOx SGP41
- 1x Адаптер 5V 2000mA
- 1x Кабель USB Type-C с углом 90°
- 4x Винты Torx T6 M1.8×10
- 1x Отвертка Torx T6
Сборка комплекта AirGradient ONE
Производитель предоставляет инструкции по сборке на этой веб-странице . Инструкции написаны четко и легко воспринимаются. По сути, требовалось установить всего три компонента, как показано ниже.
Для модуля Senseair S8 в комплекте идут два ряда штыревых разъемов: один 1×4, другой 1×5. Необходимо было установить сенсор в разъем на плате с маркировкой “CO2 Sensor”, соблюдая правильную ориентацию.
Модули SGP41 и SHT4x очень похожи и имеют одинаковое количество контактов. Поэтому важно было внимательно проверить маркировку на модулях: SGP41 устанавливается в разъем “I2C3”, а SHT4x – в разъем “SHT4x”. Стоит отметить, что цвет шелкографии на модулях может отличаться от указанного в инструкции: в данном случае SGP41 имел синюю маркировку, а SHT4x – пурпурную. При установке модулей в разъемы необходимо было убедиться, что они направлены наружу, как показано на рисунках.
Сенсор PMS использует разъем JST и в данном случае уже был подключен к плате. Оставалось только проверить надежность соединения с обеих сторон.
После установки модулей устройство можно включить, подключив кабель USB Type-C к разъему на задней панели корпуса. На задней стороне корпуса есть специальный паз для фиксации кабеля, предотвращающий его болтание и запутывание.
После включения устройство инициализируется с заводской прошивкой, OLED-экран загорается, что свидетельствует об исправной работе. Затем оставалось закрыть верхнюю и нижнюю крышки корпуса и закрепить их винтами. Теперь устройство готово к следующему шагу.
Использование монитора качества воздуха AirGradient ONE
При включении устройства на экране появляется запрос на выбор единиц измерения. Перейти в меню настроек можно длительным нажатием кнопки на задней панели. Кратковременные нажатия позволяют переключаться между вариантами единиц измерения PM2.5 и температуры, как указано ниже.
- Температура: °C, PM: µg/m³
- Температура: °C, PM: US AQI
- Температура: °F, PM: µg/m³
- Температура: °F, PM: US AQI
После выбора предпочтительных единиц измерения необходимо снова длительно нажать кнопку для сохранения настроек и перезагрузки устройства.
При первом включении устройство запрашивает подключение к WiFi. Для этого потребовалось записать серийный номер устройства, отображаемый на OLED-экране во время загрузки. Затем с помощью мобильного телефона нужно было найти точку доступа устройства с именем “AG-xxxxxx
“, где “xxxxxx
” – серийный номер устройства.
После подключения к точке доступа устройства необходимо было указать SSID и пароль целевой WiFi-сети, затем сохранить настройки для завершения конфигурации.
После экрана настроек RGB-светодиоды устройства начинают отображать цвет, соответствующий качеству воздуха, а OLED-экран показывает данные с сенсоров в выбранных единицах измерения. Комплект AirGradient ONE будет работать даже без подключения к WiFi.
Подключение к AirGradient Dashboard
Поскольку на задней панели корпуса есть USB-разъем, устройство было подключено к ноутбуку с помощью прилагаемого кабеля USB Type-C. В Arduino IDE была открыта Serial Monitor для просмотра выводимых сообщений. Было обнаружено, что устройство собирает данные измерений, включая RSSI подключенной WiFi-сети, и отправляет их на сервер AirGradient Dashboard в формате JSON. По умолчанию используется URL http://hw.airgradient.com/sensors/airgradient:xxxxx/measures
, где xxxxx
– серийный номер устройства. Однако сервер отклонил запрос с сообщением об ошибке “sensor 'airgradient:xxxxxx' unknown
“.
Для решения проблемы потребовалось создать учетную запись AirGradient Dashboard и добавить информацию об устройстве, включая местоположение и серийный номер. После успешного добавления устройства в панель управления оно было перезапущено, и через несколько секунд началась передача данных. При успешном подключении отображаются данные, как на приведенных рисунках.
Прошивка AirGradient
На момент обзора доступно два метода прошивки: прямое обновление через веб-браузер и ручная прошивка через Arduino IDE. В данном случае был выбран второй вариант. Для работы использовалась Arduino IDE 1.8.13. Исходный код был загружен с GitHub-репозитория производителя, после чего был открыт файл ONE_V9.ino . Как указано в инструкции, была выбрана платформа ESP32 и модель Lolin C3 Mini в качестве целевой платы.
Список необходимых библиотек указан в заголовке файла ONE_V9.ino. В данном случае потребовались следующие шесть библиотек. Их можно установить через менеджер библиотек или вручную, загрузив и скопировав в папку libraries Arduino.
- WifiManager от tzapu, tablatronix, тестировалась версия 2.0.11-beta
- U8g2 от oliver, тестировалась версия 2.32.15
- Sensirion I2C SGP41 от Sensation, версия 0.1.0
- Sensirion Gas Index Algorithm от Sensation, версия 3.2.1
- Arduino-SHT от Johannes Winkelmann, версия 1.2.2
- Adafruit NeoPixel от Adafruit, версия 1.11.0
Первая попытка компиляции завершилась ошибкой из-за отсутствия некоторых библиотек. Потребовалось установить дополнительные библиотеки.
- PMS Library от Markusz Kakl, версия 1.1.0
- S8 UART от Josep Comas, версия 1.0.0
- Sensirion Core от Sensirion, версия 0.6.0
После установки этих библиотек компиляция всё равно завершалась ошибкой, но проблема была в файлах PMS.h и PMS.cpp. Поиск в интернете позволил найти решение в этой публикации , где упоминалось, что производитель использовал модифицированную версию библиотеки PMS. Потребовалось обновить эти два файла следующим кодом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
#ifndef PMS_H #define PMS_H #include "Stream.h" class PMS { public: static const uint16_t SINGLE_RESPONSE_TIME = 1000; static const uint16_t TOTAL_RESPONSE_TIME = 1000 * 10; static const uint16_t STEADY_RESPONSE_TIME = 1000 * 30; static const uint16_t BAUD_RATE = 9600; struct DATA { // Standard Particles, CF=1 uint16_t PM_SP_UG_1_0; uint16_t PM_SP_UG_2_5; uint16_t PM_SP_UG_10_0; // Atmospheric environment uint16_t PM_AE_UG_1_0; uint16_t PM_AE_UG_2_5; uint16_t PM_AE_UG_10_0; // Raw particles count (number of particles in 0.1l of air uint16_t PM_RAW_0_3; uint16_t PM_RAW_0_5; uint16_t PM_RAW_1_0; uint16_t PM_RAW_2_5; uint16_t PM_RAW_5_0; uint16_t PM_RAW_10_0; // Formaldehyde (HCHO) concentration in mg/m^3 - PMSxxxxST units only uint16_t AMB_HCHO; // Temperature & humidity - PMSxxxxST units only int16_t AMB_TMP; uint16_t AMB_HUM; }; PMS(Stream&); void sleep(); void wakeUp(); void activeMode(); void passiveMode(); void requestRead(); bool read(DATA& data); bool readUntil(DATA& data, uint16_t timeout = SINGLE_RESPONSE_TIME); private: enum STATUS { STATUS_WAITING, STATUS_OK }; enum MODE { MODE_ACTIVE, MODE_PASSIVE }; uint8_t _payload[50]; Stream* _stream; DATA* _data; STATUS _status; MODE _mode = MODE_ACTIVE; uint8_t _index = 0; uint16_t _frameLen; uint16_t _checksum; uint16_t _calculatedChecksum; void loop(); char Char_PM2[10]; }; #endif |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
#include "PMS.h" PMS::PMS(Stream& stream) { this->_stream = &stream; } // Standby mode. For low power consumption and prolong the life of the sensor. void PMS::sleep() { uint8_t command[] = { 0x42, 0x4D, 0xE4, 0x00, 0x00, 0x01, 0x73 }; _stream->write(command, sizeof(command)); } // Operating mode. Stable data should be got at least 30 seconds after the sensor wakeup from the sleep mode because of the fan's performance. void PMS::wakeUp() { uint8_t command[] = { 0x42, 0x4D, 0xE4, 0x00, 0x01, 0x01, 0x74 }; _stream->write(command, sizeof(command)); } // Active mode. Default mode after power up. In this mode sensor would send serial data to the host automatically. void PMS::activeMode() { uint8_t command[] = { 0x42, 0x4D, 0xE1, 0x00, 0x01, 0x01, 0x71 }; _stream->write(command, sizeof(command)); _mode = MODE_ACTIVE; } // Passive mode. In this mode sensor would send serial data to the host only for request. void PMS::passiveMode() { uint8_t command[] = { 0x42, 0x4D, 0xE1, 0x00, 0x00, 0x01, 0x70 }; _stream->write(command, sizeof(command)); _mode = MODE_PASSIVE; } // Request read in Passive Mode. void PMS::requestRead() { if (_mode == MODE_PASSIVE) { uint8_t command[] = { 0x42, 0x4D, 0xE2, 0x00, 0x00, 0x01, 0x71 }; _stream->write(command, sizeof(command)); } } // Non-blocking function for parse response. bool PMS::read(DATA& data) { _data = &data; loop(); return _status == STATUS_OK; } // Blocking function for parse response. Default timeout is 1s. bool PMS::readUntil(DATA& data, uint16_t timeout) { _data = &data; uint32_t start = millis(); do { loop(); if (_status == STATUS_OK) break; } while (millis() - start < timeout); return _status == STATUS_OK; } void PMS::loop() { _status = STATUS_WAITING; if (_stream->available()) { uint8_t ch = _stream->read(); switch (_index) { case 0: if (ch != 0x42) { return; } _calculatedChecksum = ch; break; case 1: if (ch != 0x4D) { _index = 0; return; } _calculatedChecksum += ch; break; case 2: _calculatedChecksum += ch; _frameLen = ch << 8; break; case 3: _frameLen |= ch; // Unsupported sensor, different frame length, transmission error e.t.c. if (_frameLen != 2 * 9 + 2 && _frameLen != 2 * 13 + 2) { _index = 0; return; } _calculatedChecksum += ch; break; default: if (_index == _frameLen + 2) { _checksum = ch << 8; } else if (_index == _frameLen + 2 + 1) { _checksum |= ch; if (_calculatedChecksum == _checksum) { _status = STATUS_OK; // Standard Particles, CF=1. _data->PM_SP_UG_1_0 = makeWord(_payload[0], _payload[1]); _data->PM_SP_UG_2_5 = makeWord(_payload[2], _payload[3]); _data->PM_SP_UG_10_0 = makeWord(_payload[4], _payload[5]); // Atmospheric Environment. _data->PM_AE_UG_1_0 = makeWord(_payload[6], _payload[7]); _data->PM_AE_UG_2_5 = makeWord(_payload[8], _payload[9]); _data->PM_AE_UG_10_0 = makeWord(_payload[10], _payload[11]); // Total particles count per 100ml air _data->PM_RAW_0_3 = makeWord(_payload[12], _payload[13]); _data->PM_RAW_0_5 = makeWord(_payload[14], _payload[15]); _data->PM_RAW_1_0 = makeWord(_payload[16], _payload[17]); _data->PM_RAW_2_5 = makeWord(_payload[18], _payload[19]); _data->PM_RAW_5_0 = makeWord(_payload[20], _payload[21]); _data->PM_RAW_10_0 = makeWord(_payload[22], _payload[23]); // Formaldehyde concentration (PMSxxxxST units only) _data->AMB_HCHO = makeWord(_payload[24], _payload[25]) / 1000; // Temperature & humidity (PMSxxxxST units only) _data->AMB_TMP = makeWord(_payload[20], _payload[21]); _data->AMB_HUM = makeWord(_payload[22], _payload[23]); } _index = 0; return; } else { _calculatedChecksum += ch; uint8_t payloadIndex = _index - 4; // Payload is common to all sensors (first 2x6 bytes). if (payloadIndex < sizeof(_payload)) { _payload[payloadIndex] = ch; } } break; } _index++; } } |
Компиляция и загрузка завершились успешно. Однако обнаружилось, что устройство не подключается к WiFi. Проблема была решена путем жесткого прописывания SSID и пароля в коде. После этого устройство заработало нормально.
Открытый исходный код
Настройка исходного кода Arduino
Как упоминалось выше, исходный код доступен на GitHub, включая не только прошивку для этого набора, но и версии для других устройств, а также примеры.
Исходный код (ONE_V9.ino
) был изменен для отправки данных на собственный сервер. Для этого значение переменной APIROOT
в начале кода было заменено на URL сервера. Затем функция sendToServer()
была модифицирована для добавления серийного номера устройства в HTTP-запрос. После обновления значения строки POSTURL
в соответствии с требованиями, новая прошивка была скомпилирована и загружена на устройство. Через несколько секунд устройство начало успешно отправлять данные на сервер.

Поскольку 11 RGB-светодиодов программируемы, исходный код был дополнительно изменен для настройки их поведения. В стандартной конфигурации эти светодиоды выключены при загрузке и включаются после перехода устройства на главный экран.
Так как устройство использует библиотеку NeoPixel от Adafruit, цвет каждого светодиода можно управлять, вызывая функцию setPixelColor
с указанием индекса светодиода и значений RGB-компонентов. Поэтому в конец функции setup был добавлен код, заставляющий светодиоды случайно мигать в течение нескольких секунд перед переходом в нормальный режим.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
void setup() { ... int i; int j; int r; int g; int b; for(i = 0; i < 11; i++) { pixels.setPixelColor(i, pixels.Color(0, 0, 0)); delay(1); } pixels.show(); for(i = 0; i < 32; i++) { for(j = 0; j < 11; j++) { r = random(0, 256); g = random(0, 256); b = random(0, 256); pixels.setPixelColor( j, pixels.Color(r, g, b)); } delay(50); pixels.show(); } for(i = 0; i < 11; i++) { pixels.setPixelColor(i, pixels.Color(0, 0, 0)); } pixels.show(); } |
Файлы аппаратного дизайна
Доступны не только исходные коды, но и схемы, а также файлы печатных плат для разработчиков на этой странице . Эти файлы представлены в формате KiCad и успешно открываются в KiCad 7.0 без каких-либо проблем.
3D-печать собственного корпуса
Производитель также предоставляет разработчикам 3D-модели корпуса. Эти модели в формате STL можно загрузить на этой странице , где доступны два ZIP-файла: один для корпуса, другой для подставки.
3D-модель корпуса состоит из трех частей: нижняя часть, верхняя часть и модель области кабеля. Эти модели были визуализированы в программе MeshLab после открытия STL-файлов, как показано на следующих изображениях. Также была протестирована печать модели области кабеля: STL-файл был обработан в PrusaSlicer 2.6.0 и напечатан с использованием Printrun 2.1. Модель печаталась легко, результат показан на изображении ниже.
Заключение
Монитор качества воздуха AirGradient ONE работает очень хорошо, хотя при наблюдении за сообщениями через последовательный порт заметно, что значения измерений иногда нестабильны после перезапуска устройства. К счастью, они стабилизируются в течение нескольких секунд. Открытый исходный код устройства также позволяет настраивать прошивку, электронику и корпус.
Благодарность AirGradient за предоставление набора One Kit для этого обзора. Полностью собранную и протестированную версию AirGradient ONE можно заказать за $195, а версию набора, рассмотренную здесь, — за $138 на сайте производителя .
Выражаем свою благодарность источнику, с которого взята и переведена статья, сайту cnx-software.com.
Оригинал статьи вы можете прочитать здесь.