Как я делал датчик CO2 для умного дома на базе SCD30

Рендер платы в Autodesk Fusion

Рендер платы в Autodesk Fusion

Почему я решил сделать сам?

У меня есть две любимые сестры, которые очень хотели бы иметь дома сенсор CO2, а также температуры и влажности воздуха. Представленные на рынке готовые решения меня не сильно устраивали, а как говорится: хочешь сделать хорошо — сделай это сам. К тому же, у меня была давняя мечта сделать свою собственную плату.

Выбор компонентов

Я сам пользовался сенсорами углекислого газа MH-Z19 и SHT31-D для температуры и влажности воздуха, которые навесным монтажом были приделаны к ESP32.

Я искал сенсор, который бы по возможности имел бы более удачный форм-фактор и по возможности бы сочетал в мог бы измерять все три характеристики. В списке поддерживаемых сенсоров на сайте ESPHome я обнаружил SCD30 от фирмы Senserion.

Цельная плата сенсора, с большой камерой для ИК CO2 сенсора. Поддерживает подключение по I2C и ModBus.

Цельная плата сенсора, с большой камерой для ИК CO2 сенсора. Поддерживает подключение по I2C и ModBus.

Помимо компактных размеров и позитивных отзывов на форумах, также приятно порадовала цена в районе $15 за штуку.

В качестве контроллера решил использовать ESP8266 за отсутствием потребности в Bluetooth и использовании одного единственного I2C устройства.

Исходя из того, что устройством будут пользоваться люди далекие от мира микроконтроллеров и электроники, было решено использовать USB-C для устройства и снабдить каждую плату встроенным USB-UART конвертером для загрузки прошивки. Здесь бы я снова хотел обратить внимание на экосистему ESPHome, в которой прошивка конфигурируется yaml файлом, компилируется и загружается программой на Python в автоматическом режиме. Помимо всего прочего работает как под Widnows, так и под MacOS, и под Linux без каких либо проблем.

Разработка платы

Так как изначально планировалось заказывать платы на JLPCB, то я решил воспользоваться их средой EasyEDA Pro (которая бесплатна ровно так же как и стандартная версия).

Для схемы подключения UART конвертера были взяты референсы из официальной документации ESP. Использование RTS и DTR пинов с двумя транзисторами позволяют загрузчику прошивки перезагружать плату в режим прошивки без активного участия пользователя.

Логическая схема подключения сенсора и контроллера. Подтягивающие резисторы для I2C шины находятся непосредственно на SCD30, поэтому на данной схеме они отсутствуют.

Логическая схема подключения сенсора и контроллера. Подтягивающие резисторы для I2C шины находятся непосредственно на SCD30, поэтому на данной схеме они отсутствуют.

Затем перепробовал порядка 5 вариантов расположения элементов и остановился на следующем:

Красный - верхний слой, синий - нижний, жёлтый - шелкография, зелёный кружок - температурный сенсор на плате SCD30.

Красный — верхний слой, синий — нижний, жёлтый — шелкография, зелёный кружок — температурный сенсор на плате SCD30.

Основной мотивацией для такого расположения элементов были две причины:

  • Максимальное удаление температурного сенсора SCD30 от ESP8266, расположенного на нижней стороне платы сенсора, на месте зеленого кружочка

  • Нахождение сенсора ниже контроллера, при вертикальной ориентации устройства (при этом USB-C на нижней части устройства) для избежания искажения температуры от теплого потока конвекции с ESP8266

Интересно было также, будет ли эффект конвекции играть хоть какую-то значительную роль для циркуляции воздуха. Для этого я провёл симуляцию в Autodesk Fusion для горизонтального и вертикального расположения схемы в корпусе с отверстиями.

Симуляции воздушных потоков

Вертикальное расположение платы с сенсорами, жёлтая стрелка - направление гравитации.

Вертикальное расположение платы с сенсорами, жёлтая стрелка — направление гравитации.

Горизонтальное расположение платы с сенсорами, жёлтая стрелка - направление гравитации.

Горизонтальное расположение платы с сенсорами, жёлтая стрелка — направление гравитации.

Температурный эффект от нагрева не отличался от ориентации платы, а скорость потока воздуха отличается несущественно (в принципе, как и ожидалось). Не уверен, правда, можно ли считать симуляцию правдоподобной для таких слабых скоростей потока и мощности нагреваемого элемента.

Заказ и изготовление платы

Для большей экономической целесообразности решил заказать сразу 10 плат, тем более что есть ещё знакомые, коллеги и родственники, которые были заинтересованы в таком продукте.

Так как ни опыта пайки SMD компонентов, ни самих SMD компонентов у меня в наличии не было, а ассортимент и стоимость последних на digikey и mouser меня категорически расстроили, решил воспользоваться сервисом PCB Assembly на том же JLPCB. Там же можно было бы заказать и распайку ESP8266, но тогда надо было бы выбирать более дорогой пакет обслуживания и довольно дорого платить за сам контроллер, так что их я заказал отдельно на AliExpresse (см. ссылки внизу статьи)

Итого за 10 плат, доставкой FedEx в Германию и купоном на скидку в $10 (на первую покупку) у меня вышел заказ на $76.77. Заказал я платы 2 го января, а 9 го января в 7:38 утра курьер вручил мне коробку с готовыми платами.

Готовый платки со всем кроме SCD30 и ESP8266

Готовый платки со всем кроме SCD30 и ESP8266

Затем моими не очень прямыми руками были припаяны сенсор и плата ESP8266.

Изначально я планировал вставить подставочки между сенсором и платой, но доступный мне 3D принтер не смог напечатать такие мелкие детальки. Токарного станка тоже нет под рукой, да и в целом сенсор неплохо держится на 4ех контактах.

Изначально я планировал вставить подставочки между сенсором и платой, но доступный мне 3D принтер не смог напечатать такие мелкие детальки. Токарного станка тоже нет под рукой, да и в целом сенсор неплохо держится на 4ех контактах.

Прошивка

При использовании Home Assistant (HASS) никаких особых нюансов нет. Сенсор в один клик интегрируется в умный дом.

Изначальная цель была разработать устройство, которое могло бы работать самодостаточно. Я не считаю целесообразным настройку HASS ради одного единственного устройства. Также не хотелось бы изобретать велосипед, а использовать ESPhome как основу.

Среди готовых компонентов для ESPhome есть web_server, который поднимает на самом контроллере простенький веб интерфейс с датчиками и возможностью обновления прошивки.

Пример интерфейса для ESPHome

Пример интерфейса для ESPHome

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

Для этого пришлось написать свой небольшой компонент history_container, который подписывается на обновления сенсора и запоминает N последних значений и разницу во времени между измерениями.

Также был чуть-чуть доработан фронтенд, для поддержки контейнера и между делом исправлен селектор CSS, который не работал в Safari и Firefox.

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

Калибровка контроллера

В процессе тестирования устройств заметил, что температура SCD30 разительно отличается от моего старого сенсора SHT31-D в большую сторону, а значит нужно провести калибровку.

В пользу достоверности значений старого сенсора также говорил встроенный температурный сенсор в MH-Z19, показывающий похожие значения, и здравый смысл.

Для этого оставил оба сенсора подключенными к HASS на несколько часов с закрытым окном. Затем выгрузил оба ряда значений и нехитрым образом высчитал константу, которая минимизирует среднеквадратичное отклонение между двумя рядами.

Код для калибровки

import pandas as pd
import numpy as np

# csv файл экспортированный из Home Assistant
df = pd.read_csv("history-0010.csv")

# выбор референсного и сенсора для калибровки 
true_values = df.loc[(df['entity_id'] == 'sensor.bedroom_sensor_bedroom_temperature')].drop(columns=['entity_id'])
to_calibrate = df.loc[(df['entity_id'] == 'sensor.scd30_0010_scd30_temperature')].drop(columns=['entity_id'])

# почему-то значения не распарсились как числа
true_values['state'] = true_values['state'].astype(float)
to_calibrate['state'] = to_calibrate['state'].astype(float)

# конвертируем строки в дату
true_values['last_changed'] = pd.to_datetime(true_values['last_changed'])
to_calibrate['last_changed'] = pd.to_datetime(to_calibrate['last_changed'])

# присваиваем дату как индекс
true_values = true_values.set_index('last_changed')
to_calibrate = to_calibrate.set_index('last_changed')

# сортируем оба ряда
true_values = true_values.sort_values('last_changed')
to_calibrate = to_calibrate.sort_values('last_changed')

# приводим оба ряда к общей временной шкале 
df_merged = pd.merge_asof(true_values, to_calibrate, on='last_changed', direction='nearest', suffixes=('_true', '_calib'))
# считаем среднее квадратичное отклонение
def calculate_rmse(offset):
    diff = (df_merged['state_true'] - (df_merged['state_calib'] + offset)) ** 2
    rmse = np.sqrt(diff.mean())
    return rmse

# диапазон для поиска оффсета
offset_range = np.linspace(-10, 0, 1000)

rmse_values = [calculate_rmse(offset) for offset in offset_range]

best_offset = offset_range[np.argmin(rmse_values)]

print(f"The optimal offset that minimizes RMSE is: {best_offset:.2f}")

Получаем следующий график для референсного сенсора и нового сенсора до и после калибровки

7490b88608bf40c430431df414ea2fc6.png

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

Я бы мог понять, если бы после изменения оффсета непосредственно во время работы устройства сопровождалось временной задержкой. Это означало бы наличие буфера последних значений температуры для уменьшения шума. Задержка бы обуславливалась замещением значений в буфере новыми значениями с оффсетом (да и то, можно было бы применять коррекцию после высчитывания среднего значения буфера).

Если задержка присутствует и отключения питания, это наводит на мысль, что буфер последних значений находится в энергонезависимой памяти. Может быть этим разработчики хотели уменьшить шум для первых измерений температуры после включения, предполагая, что температура до и после включения не изменилась так сильно.

Заключение

Окончательным результатом я более чем доволен. Точность измерения и возможность кастомизации, полная открытость проекта и удобство пользования программным обеспечением (EasyEDA и Autodesk Fusion) приятно удивили.

Суммарная стоимость одной единицы составила $24, расчёт: $76.77 / 10 (платы с компонентами) + $1.43 (ESP8266) + $14.89 (SCD30) и является полностью кастомизируемым и простым в использовании устройством.

Ссылки

  1. SCD30 на AliExpress (https://de.aliexpress.com/item/1005007654300194.html)

  2. ESP8266 на AliExpress (https://de.aliexpress.com/item/1005007014587323.html)

  3. Ссылка на репозиторий GitHub со всеми файлами и описанием проекта https://github.com/lrlunin/scd30-esphome

  4. Ссылка на проект EasyEDA https://oshwlab.com/lunin.leonid/scd_with_pins_uart

© Habrahabr.ru