Доброго времени суток, хабровчане!

Небольшое предисловие


Намедни я стал счастливым владельцем одного из самых маленьких одноплатников, работающим на LEDE, и первое, что захотелось сделать (после моргания светодиодом) — домашняя метеостанция, к которой можно получить доступ из любой точки. Первым делом было решено снимать данные о температуре, влажности и давлении. Для этого были выбраны купленные ранее датчики DHT11 И LPS3311AP (фото под катом).

Фото для интересующихся
image

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

Какой выбор остаётся?

  1. Собрать прослойку на микроконтроллёре ATmega328, прошить, залить готовый код и читать с неё. Очень увлекательно, но звучит как попытка собрать велосипед для дальнейшего использования в качестве костыля.
  2. Читать «вручную» с I2C, опираясь на официальный даташит. Попробовал, это возможно, но плодить bash-скрипты не хотелось и не казалось методологически верным.
  3. Написать библиотеку, чтобы работать с датчиком так, как хочется.

Если интересно, как шло и что из этого получилось, то добро пожаловать под кат.

Первый вопрос, с которым я столкнулся — выбор языка. Из того, что работает на этом одноплатнике без танцев с бубном — C++ и Python. Так как в дальнейшем я хочу положить этот проект в основу умного дома для себя, то выбор пал на последний.

Из-за эффекта утёнка следующий вопрос (какую версию) я не поднимал и сразу же поставил облегчённый python3 и библиотеку для работы с I2C шиной.

Дальше были часы курения сначала даташита, потом — software guide от компании производителя. Они позволили быстро подготовить сенсор к работе (хотя софтверный гайд оказался более полным, в том числе в части рекомендации проверять завершения измерения и «перезагрузки» сенсора для более точного сбора.

Первая сложность, с которой я столкнулся — это чтение данных. Так как данные с термометра передаются двумя байтами, а давления — тремя, то необходимо получить несколько байт и объединить их в одно большое число. Но python по умолчанию преобразует hex в int, и простая конкатенация не работает. Преобразование int в hex возвращает строку, которая прекрасно объединяется, но при этом не преобразуется обратно в число. Выход? Можно было бы подключить поддержку с-типов, но возиться, равно как забивать память дополнительной библиотекой, не хотелось, поэтому было решено написать функцию на 7 (на самом деле 8, если считать словарь) строк для перевода псевдобайтстроки в число.

Hex containing string to int
s_t_h = { '0' : 0,  '1' : 1,  '2' : 2,  '3' : 3,   
               '4' : 4,  '5' : 5,  '6' : 6,  '7' : 7,
               '8' : 8,  '9' : 9,  'a' : 10, 'b' : 11,
               'c' : 12, 'd' : 13, 'e' : 14, 'f' : 15 }

def __string_to_int(self, hex_string):
        l = len(hex_string) - 1
        res = 0
        for h in hex_string:
            res += s_t_h[h] * (16 ** l)
            l -= 1
        return res

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

Вторая сложность, которая меня поставила в тупик, это трактовка данных. Оба указанных выше документа дают весьма простые формулы для преобразования:

Pout(mbar)=(PRESS_OUT_H & PRESS_OUT_L & PRESS_OUT_XL)[dec] / 4096 для давления;
T(degC) = 42.5 + (Temp_OUTH & TEMP_OUT_L)[dec] / 480 для температуры.

Что радует, они сразу предлагают метрическую систему.

Однако, первый подход упорно давал мне результат 0x2F8000, что означает 760 миллибар или примерно 585 мм рт. ст. Для высоты в 130 метров над уровнем моря это явно маловато. Перепроверка кода, перечитывание информации об измерении давления, перезагрузка датчика и игры с точностью ничего не дали. А вот повторное изучение гайдлайна помогло — стабильные 760 мбар трактуются как сигнал, что датчик неисправен. Читатели, которые внимательно посмотрели на приложенное фото в начале статьи могут убедиться, что датчик, более того, в принципе отсутствует на чипе :) На моё счастье, я запасливо купил сразу два таких.

Фото исправного чипа и правильной работы


Маленький чёрный квадратик в центре — это и есть измерительная пластина HCLGA-16L, «сердце» сенсора.

image

После этого открытия стало очевидным, что надо бы сделать проверку на исправность. так родилась функция:

working_check
BROKEN_MARKER = 0x2f8000/4096
 
    def __working_check(self, address):
        c1 = self.__read_pressure(address)
        c2 = self.__read_pressure(address)
        c3 = self.__read_pressure(address)
        if c1 == c2 == c3 == BROKEN_MARKER:
            return True
        else:
            return False

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

Тут же в голову пришла мысль, что не плохо бы при инициализации проверять, верно ли указан адрес на шине, и была добавлена функция __deviceAdressCheck, проверяющая регистр WHO_AM_I и ожидающая получить заветное число 0хВВ.

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

Комментарии (4)


  1. SatCat
    17.10.2019 07:21

    "'d': 13, 'e': 14, 'f': 16"
    точно 16?


    1. Dennic Автор
      17.10.2019 07:22

      Спасибо за комментарий, исправил в статье и на гитхабе.


  1. ZEN_LS
    17.10.2019 19:20

    Лучше было написать модуль ядра, 2 сенсора представляли бы 2 файла в каталоге /dev, и читать показания можно бы было простой командой cat, и сделать это можно на буквально любом языке. Так-же в любой линукс железке без танцев бубном можно писать на sh и с, так как без них невозможен линукс.


  1. ValeriyS
    17.10.2019 20:21

    Для похожих целей использую комбинированный сенсор температуры, влажности и давления BME280, точнее платку на его основе: www.amazon.com/gp/product/B0118XCKTG
    Подключил его к Raspberry PI 3B, библиотеки на Python можно легко найти в сети. Особенно поразила одна из его характеристик — BME280 измеряет давление с шумом в 0.2 Pa RMS, что эквивалентно 1.7 см воздушного столба (сантиметров, Карл!).