История взаимодействия «чайника» и DS18B20 посредством Raspberry Pi с отправкой данных на narodmon.ru

    Доброе время суток, уважаемое хабрасообщество!
    Неделю назад я наконец-то дождался свою Малинку. Этот пост о том, как можно набить шишки в такой простой задаче как работа с датчиками температуры.
    Кому интересно — добро пожаловать под кат.

    Итак, став обладателем Малинового счастья с 512 МБ ОЗУ на борту, заново начал пересматривать множество постов, в том числе на Хабре, про возможные варианты создания «Умного дома». Но поскольку мое образование далеко от IT, решил не делать сразу Алису, а начал с простого — контроля температуры.
    Установку системы на Малинку я опущу, эта тема достаточно детально описана тут и тут. Перейду сразу к настройке Малинки для автоматизации контроля температуры с хранением и отображением результатов через веб.
    Я ориентировался на связку 1-wire, ds18b20 и rrdtool, как самый простой и доступный вариант.

    1. Установка софта

    Патч ядра для 1-wire
    cd /boot
    wget www.frank-buss.de/raspberrypi/kernel-rpi-w1.tgz
    tar -xzf kernel-rpi-w1.tgz
    rm -f kernel-rpi-w1.tgz
    cd /lib/modules
    wget www.frank-buss.de/raspberrypi/modules-rpi-w1.tgz
    tar -xzf modules-rpi-w1.tgz
    rm -f modules-rpi-w1.tgz
    sync
    reboot

    Установка libwww-perl и rrdtool
    sudo apt-get install libwww-perl rrdtool

    2. Создание необходимых скриптов

    Скрипт создания базы данных create_db.sh
    #!/bin/bash
    rrdtool create multirPItemp.rrd --step 300 \
    DS:in_temp:GAUGE:600:0:50 \
    DS:out_temp:GAUGE:600:-30:50 \
    RRA:AVERAGE:0.5:1:12 \
    RRA:AVERAGE:0.5:1:288 \
    RRA:AVERAGE:0.5:12:168 \
    RRA:AVERAGE:0.5:12:720 \
    RRA:AVERAGE:0.5:288:365

    Маленькая ремарка к скрипту создания БД. В скрипте я задал температурные диапазоны для датчика на улице и в квартире. --step 300 — интервал хранения данных температуры — 300 сек (5 минут). 600 — параметр в секундах, при отсутствии в течение которых значений температуры ее значение становится «UNKNOWN».

    Скрипт получения температуры из датчиков get_temp.pl
    #!/usr/bin/perl
    use warnings;
    &check_modules;
    &get_device_IDs;

    foreach $device ( @deviceIDs)
    {
    $reading = &read_device($device);
    if ($reading != «9999»)
    {
    push(@temp_readings,$reading);
    }
    }

    #update the database
    `/usr/bin/rrdtool update /home/pi/temperature/multirPItemp.rrd N:$temp_readings[0]:$temp_readings[1]`;

    print «Temp 1 = $temp_readings[0] Temp 2 = $temp_readings[1]\n»;

    #######################################################################
    # Дополнения для narodmon.ru. По материалам habrahabr.ru/post/166373
    open(FILE, ">/home/pi/temperature/temp_out");
    print FILE "$temp_readings[0]";
    close (FILE);
    open(FILE, ">/home/pi/temperature/temp_in");
    print FILE "$temp_readings[1]";
    close (FILE);
    #######################################################################

    sub check_modules
    {
    $mods = `cat /proc/modules`;
    if ($mods =~ /w1_gpio/ && $mods =~ /w1_therm/)
    {
    print «w1 modules already loaded \n»;
    }
    else
    {
    print «loading w1 modules \n»;
    `sudo modprobe w1-gpio`;
    `sudo modprobe w1-therm`;
    }
    }

    sub get_device_IDs
    {
    # The Hex IDs off all detected 1-wire devices on the bus are stored in the file
    # «w1_master_slaves»

    # open file
    open(FILE, "/sys/bus/w1/devices/w1_bus_master1/w1_master_slaves") or die(«Unable to open file»);

    # read file into an array
    @deviceIDs = ;

    # close file
    close(FILE);
    }

    sub read_device
    {
    #takes one parameter — a device ID
    #returns the temperature if we have something like valid conditions
    #else we return «9999» for undefined

    $readcommand = «cat /sys/bus/w1/devices/».$_[0]."/w1_slave 2>&1";
    $readcommand =~ s/\R//g;
    $sensor_temp = `$readcommand`;

    if ($sensor_temp !~ /No such file or directory/)
    {
    if ($sensor_temp !~ /NO/)
    {
    $sensor_temp =~ /t=(\d+)/i;
    $temperature = (($1/1000));
    }
    else
    {
    $ret = «9999»;
    }
    }
    else
    {
    $ret = «9999»
    }
    }

    Это первоначальный вид скрипта, такой каким я его нашел на просторах интернета. Подключенные датчики, находящиеся в комнате, показывали адекватные результаты, Но когда я вынес один датчик на улицу на ПМЖ, я был разочарован тем, что температуру показывало ровно 0 градусов цельсия, что бы я ни делал (а на улице у нас сейчас -5...-10).
    Как оказалось, все дело в этой строке:
    $:sensor_temp =~ /t=(\d+)/i;
    которая предполагает получение температуры с датчика без учета отрицательных температур. Ввиду отсутствия соответствующих знаний я потратил пол-часа и пару чашек кофе на приведение строки к виду:
    $:sensor_temp =~ /t=(\D*\d+)/i;
    что позволило нормально заносить в БД и отрицательные температуры.

    Скрипт создания графиков create_graphs.sh
    #!/bin/bash
    RRDPATH="/home/pi/temperature/"
    INCOLOUR="#990000"
    OUTCOLOUR="#009900"
    TRENDCOLOUR1="#FF0000"
    TRENDCOLOUR2="#00FF00"
    #hour
    rrdtool graph $RRDPATH/mhour_in.png --start -6h --alt-autoscale \
    DEF:intemp=$RRDPATH/multirPItemp.rrd:in_temp:AVERAGE \
    DEF:outtemp=$RRDPATH/multirPItemp.rrd:out_temp:AVERAGE \
    CDEF:intrend=intemp,1200,TREND \
    CDEF:outtrend=outtemp,1200,TREND \
    LINE2:intemp$INCOLOUR:"Inside" \
    LINE1:intrend$TRENDCOLOUR1:"20 min AVG" \
    #LINE2:outtemp$OUTCOLOUR:"Outside" \
    #LINE1:outtrend$TRENDCOLOUR2:"20 min AVG"
    
    #hour outside
    rrdtool graph $RRDPATH/mhour_out.png --start -6h --alt-autoscale \
    DEF:intemp=$RRDPATH/multirPItemp.rrd:in_temp:AVERAGE \
    DEF:outtemp=$RRDPATH/multirPItemp.rrd:out_temp:AVERAGE \
    CDEF:intrend=intemp,1200,TREND \
    CDEF:outtrend=outtemp,1200,TREND \
    LINE2:outtemp$OUTCOLOUR:"Outside" \
    LINE1:outtrend$TRENDCOLOUR2:"20 min AVG"
    
    #day
    rrdtool graph $RRDPATH/mday.png --start -1d --alt-autoscale \
    DEF:intemp=$RRDPATH/multirPItemp.rrd:in_temp:AVERAGE \
    DEF:outtemp=$RRDPATH/multirPItemp.rrd:out_temp:AVERAGE \
    CDEF:intrend=intemp,1800,TREND \
    CDEF:outtrend=outtemp,1800,TREND \
    LINE2:intemp$INCOLOUR:"Inside" \
    LINE1:intrend$TRENDCOLOUR1:"1h min AVG" \
    LINE2:outtemp$OUTCOLOUR:"Outside" \
    LINE1:outtrend$TRENDCOLOUR2:"1h min AVG"
    
    #week
    rrdtool graph $RRDPATH/mweek.png --start -1w --alt-autoscale \
    DEF:intemp=$RRDPATH/multirPItemp.rrd:in_temp:AVERAGE \
    DEF:outtemp=$RRDPATH/multirPItemp.rrd:out_temp:AVERAGE \
    LINE2:intemp$INCOLOUR:"Inside temperature" \
    LINE2:outtemp$OUTCOLOUR:"Outside temperature" \
    
    #month
    rrdtool graph $RRDPATH/mmonth.png --start -1m --alt-autoscale \
    DEF:intemp=$RRDPATH/multirPItemp.rrd:in_temp:AVERAGE \
    DEF:outtemp=$RRDPATH/multirPItemp.rrd:out_temp:AVERAGE \
    LINE2:intemp$INCOLOUR:"Inside temperature" \
    LINE2:outtemp$OUTCOLOUR:"Outside temperature" \
    
    #year
    rrdtool graph $RRDPATH/myear.png --start -1y --alt-autoscale \
    DEF:intemp=$RRDPATH/multirPItemp.rrd:in_temp:AVERAGE \
    DEF:outtemp=$RRDPATH/multirPItemp.rrd:out_temp:AVERAGE \
    LINE2:intemp$INCOLOUR:"Inside temperature" \
    LINE2:outtemp$OUTCOLOUR:"Outside temperature" \

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

    После этого я создал скрипт последовательного запуска получения температур с датчиков и генерирования графиков get.sh
    #!/bin/bash
    /home/pi/temperature/get_temp.pl
    /home/pi/temperature/create_graphs.sh

    и добавил в crontab -e строку его запуска:
    */5 * * * * /home/pi/temperature/get.sh

    что позволяет запускать скрипт автоматически каждые 5 минут.
    Это все отображается на простенькой веб-страничке.

    2.1. Подключение к narodmon.ru

    По просьбе трудящихся добавляю информацию о подключении всего этого хозяйства для пересылки данных на narodmon.ru. Делал по материалам отсюда.
    Итак:
    1. Вносим изменения в скрипт получения данных из датчиков с целью записи текущих результатов, каждого в свой файл (добавил код в скрипт, см. выше).
    2. Устанавливаем (если еще не стоит)
    sudo apt-get install php5-cgi
    3. Создаем скрипт отправки данных из файлов с показаниями на сайт narodmon.ru
    Скрипт отправки данных на narodmon.ru - send.php
    #!/usr/bin/php-cgi -q
    <?
    $file_name="/home/pi/temperature/temp_out";
    $file=fopen("$file_name", «r»); 
    $gradus_out=fread($file, filesize($file_name));
    echo "$gradus_out\n";
    fclose($file);
    
    $file_name="/home/pi/temperature/temp_in";
    $file=fopen("$file_name", «r»); 
    $gradus_in=fread($file, filesize($file_name));
    echo "$gradus_in\n";
    fclose($file);
    
    $fp = @fsockopen(«tcp://narodmon.ru», 8283, $errno, $errstr);
    if(!$fp) exit(«ERROR(».$errno."): ".$errstr);
    fwrite($fp, "#01-23-45-67-89-AF\n#0123456789ABCDEF#$gradus_out\n#0123456789ABCDEF#$gradus_in\n##");
    fclose($fp);
    ?>
    

    01-23-45-67-89-AF — МАС-адрес сетевой карты Малинки, 0123456789ABCDEF — серийные номера датчиков температуры.
    4. Добавляем в crontab строку для регулярной отправки информации на narodmon.ru (на сайте есть ограничение на частоту приемки информации — не чаще раза в 5 минут, поэтому я сделал отправку каждые 10 минут):
    */10 * * * * /home/pi/temperature/send.php

    5. Регистрируемся на проекте narodmon.ru и добавляем МАС-адрес Малинки. Теперь статистика ведется и у них :)

    3. Теперь переходим к железу

    Порывшись в закромах кладовки, я обнаружил у себя двухрядный коннектор типа «мама» и телефонный удлинитель. От коннектора я откусил 2х4 и подпаял резистор 4k7 между ножкой 1 и ножкой 7 (на ножке 1 у нас +3В, ножка 6 — земля, ножка 7 — PIN4, который в Малинке позволяет общаться по протоколу 1-wire).

    В удлинитель был впаян первый датчик ds18b20, а второй датчик я подпаял напрямую к телефонному кабелю, замотал черной (не синей) изолентой и через бывшее отверстие для кабеля спутниковой антенны вывел за пределы квартиры на улицу.
    В собранном виде с учетом размещения Малинки в транспортировочном корпусе получилась следующая картина:

    Как оказалось, использование микротройничка — очень удобная штука :)
    Ну и напоследок — картинка с сайта.

    Результат на проекте narodmon.ru.

    Вместо послесловия

    Если вы ожидали увидеть подключение датчиков температуры к чайнику — приношу свои извинения :) «Чайник» в данной статье — это я. Понемногу разбирался с вопросом в меру сил и знаний, заглядывая в Гугл и используя метод проб и ошибок.
    Поставленной цели я добился. Буду признателен всем за подсказки по улучшению или правильному подходу к достижению локальных целей. Спасибо за внимание :)

    З.Ы. Ссылку на страничку с графиками не дам — «ляжет» «Малинка» ;)
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 34
    • +5
      А я уж было хотел порекомендовать термопот)
      • +21
        А я читал статью в надежде увидеть чайник под управлением Linux :(
      • 0
        Советую посмотреть на openHAB, может пригодиться. Да мне и самому интересно, как он будет чувствовать себя на ARM, теоретически должен встать без особых проблем.
        • 0
          К сожалению, скорее всего не встанет. Он на яве, а софт-флоат сборка работает с плавающими раз в 10 медленнее, к тому же дата сборки аж 28 августа, и все еще залочена под 256 МБ ОЗУ. Wheezy с хард-флоатом обновляется регулярно.
        • 0
          С помощью GPIO к Raspberry Pi можно подключить только одну сеть 1-wire или несколько?
          • 0
            Дефолтный драйвер подразумевает, что 1-wire подключен к GPIO4. Он же (драйвер) и используется в статье.
            Но ничто не мешает самостоятельно реализовать 1-wire програмно на любом из пинов.
            • 0
              Понятно, что драйвер можно поправить, вопрос был в том, можно ли использовать несколько сетей одновременно, например, работать с устройствами вроде DS2408 на одной сети во время опроса термометра на другой (если не всё в порядке с проводами, это может занимать несколько секунд).
          • 0
            Ох уж этот 1wire, ненавижу этот протокол.
          • 0
            Пардон за оффтоп, но 24-25 градусов в квартире не слишком много? Или просто датчик расположен близко к батареям?
            • 0
              Просто я живу в 9-этажке на среднем этаже, а топят хорошо :)
            • 0
              А почему не привели схему подключения? Схема подключения для датчика DS18B20 и DS18B20P будет отличаться, т.к. DS18B20P с паразитным питанием.
              • 0
                Хм, вторую схему вижу впервые. По датащиту (хорошее слово получилось) ds18b20 подключается двумя способами: с внешним питанием (в этом случае используется сопротивление 4,7кОм) или с паразитным питанием (в этом случае используется транзистор). В тексте я писал про наличие сопротивления на 4,7кОм, и мне казалось, что этого достаточно для того, чтобы иметь представление о способе подключения датчиков.
              • 0
                На графике Hourly inside по шкале Y три раза идет «24» — это разновидность самовнушения у малинки?
                • 0
                  Видимо, это разновидность глюка у rrdgraph. Я в скрипте создания графиков использовал параметр --alt-autoscale, что выбирает шкалу по Y с минимальной добавкой вверх и вниз от максимального и минимального значения диапазона, чтобы максимально заполнять область графика. Возможно, у rrdgraph стоит ограничение на количество значащих цифр в шкале, отсюда и такая особенность.
                  • 0
                    Оказывается, есть такая опция как --alt-y-grid. rrd автоматически определяет шаг сетки, а эта опция обеспечивает автоматический выбор формы представления значений, чтобы была видна разница. Добавил во все графики. Обновил скрин в статье.
                • 0
                  >>В скрипте я указал интервал хранения температур — 600 сек. (5 мин.)

                  Ммм?
                  • 0
                    Мдя, видимо промахнулся )))
                    • 0
                      Подправил текст с описанием.
                  • +5
                    Возьмите слово «чайник» в заголовке в кавычки. А то многие, к сожалению, увидели не то, что ожидали.
                    • 0
                      Отдельный орден полагается за ковыряние в перле будучи «чайником»! =)
                      • 0
                        Относительно Патч ядра для 1-wire: Если Вы установили свежий Raspbian “wheezy”, то патч не нужен, все модули уже на борту. Просто загружаем их
                        sudo modprobe w1-gpio
                        sudo modprobe w1_therm
                        


                        Чтобы они загружались при старте системы
                        добавляем в /etc/modules
                        w1-gpio
                        w1_therm
                        
                        • 0
                          Можете дополнить описание как научить «малинку» отправлять данные на Народный мониторинг для отображения статистики и графиков Google Chart c привязкой к карте мира?
                          PS Протокол передачи доступен после регистрации на основе email адреса.
                          PPS В случае успеха добавлю Ваше решение в каталог поддерживаемых решений ибо «малиновая» аудитория растет и вопросов по ее использованию все больше и больше приходит.
                          • 0
                            Это детально расписано здесь. Делал 1 к 1. Но если есть необходимость в том, чтобы все было в одном месте — пишите, внесу коррективы в эту статью.
                            • +1
                              Я только ЗА и считаю правильнее дополнить Вашу статью
                              • 0
                                Ок, сейчас дополню.
                                • 0
                                  отлично, теперь тема раскрыта полностью)
                              • 0
                                В указанной статье упор сделан именно на отправку с роутера MR3020, а не «Малинки», что весьма не 1 к 1. Поэтому ожидается отдельное описание в т.ч. и взаимодействия.
                                • 0
                                  Ну там для малинки приведен отдельный скрипт. Я его 1 к 1 без переделки взял — все работает отлично. Если бы были особенности-сложности, я бы написал в комментарии.
                                  • 0
                                    Упора на роутер нет, просто большая часть работы была проведена по нему, вот и описания больше, а по молинке большая часть работы в этой статье, а у меня оставшаяся работа описана.
                              • 0
                                Не получалось завеси датчик, от отчаяния решил установить патч 1-wire, хотя знал, что это лишнее. В результате Raspberry перестал грузиться, что было печально, ведь дело было в поле, в 50км от монитора с HDMI. Стоит обновить статью и указать в ней, что не надо этого делать) Хоть она и старая, в поиске вылезает в топ10
                                • 0
                                  Поднял под Orange PI, тоже работает. Правда в Perl скрипте кавычки испортились, пришлось исправить чуток.
                                  Вот правильный вариант, если кому интересно. Сервер использовал lighttpd.

                                  #!/usr/bin/perl
                                  
                                  use warnings; 
                                  #use diagnostics;
                                  &check_modules; 
                                  &get_device_IDs;
                                  
                                  
                                  
                                  foreach $device(@deviceIDs) {
                                      $reading = & read_device($device);
                                      if ($reading != "9999") {
                                          push(@temp_readings, $reading);
                                  		push(@temp_readings, $reading);
                                      }
                                  }
                                  
                                  #update the database
                                  `/usr/bin/rrdtool update /home/RRD/multirPItemp.rrd N:$temp_readings[0]:$temp_readings[1]`;
                                  #`/usr/bin/rrdtool update /home/RRD/multirPItemp.rrd N:$temp_readings[0]`;
                                  
                                  
                                  print "Temp 1 = $temp_readings[0]\n";
                                  print "Temp 2 = $temp_readings[1]\n";
                                  
                                  
                                  ########################################################################
                                  #Дополнения для narodmon.ru.По материалам habrahabr.ru/post/166373
                                  # open(FILE, ">/home/RRD/pi/temperature/temp_out");#
                                  #print FILE "$temp_readings[0]";#
                                  #close(FILE);#
                                  #open(FILE, ">/home/RRD/temp_in");#
                                  #print FILE "$temp_readings[1]";#
                                  #close(FILE);
                                  #######################################################################
                                  
                                  sub check_modules {
                                      $mods = `cat /proc/modules`;
                                  
                                  	if ($mods =~ /w1_gpio/ && $mods =~ /w1_therm/) {
                                          print "w1 modules already loaded \n";
                                      } else {
                                          print "loading w1 modules \n";
                                          `sudo modprobe w1-gpio`;
                                          `sudo modprobe w1-therm`;
                                      }
                                  
                                  }
                                  
                                  
                                  
                                  sub get_device_IDs {#
                                      #The Hex IDs off all detected 1 - wire devices on the bus are stored in the file
                                  	# "w1_master_slaves"
                                  
                                      #open file
                                      open(FILE, "/sys/bus/w1/devices/w1_bus_master1/w1_master_slaves") or die("Unable to open file");
                                  
                                      #read file into an array
                                  	@deviceIDs = <FILE>;
                                  
                                      #close file
                                      close(FILE);
                                  }
                                  
                                  sub read_device {
                                      #takes one parameter — a device ID
                                  	#returns the temperature
                                      #if we have something like valid conditions
                                      #else we return "9999" for undefined
                                  
                                  
                                      $readcommand = "cat /sys/bus/w1/devices/".$_[0]."/w1_slave 2>&1";
                                      $readcommand =~ s/\R//g;
                                      $sensor_temp = `$readcommand`;
                                  
                                      if ($sensor_temp!~/No such file or directory/) {
                                          if ($sensor_temp!~/NO/) {
                                              $sensor_temp =~ /t=(\d+)/i;
                                              $sensor_temp =~ /t=(\D*\d+)/i;
                                              $temperature = (($1 / 1000));
                                          } else {
                                              $ret = "9999";
                                          }
                                      } else {
                                          $ret = "9999";
                                      }
                                  }
                                  
                                  • 0
                                    а, да, в модуле check_modules надо перед `sudo modprobe w1-gpio`; надо добавить `modprobe w1-sunxi`;

                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.