Nov 13, 2011

Python + Tornado + RRDTool: температура на улице и в серверной

Рассмотрим как можно мониторить температуру при помощи сервера. Алгоритм довольно простой: подключаем датчик температуры к компу, пишем скрипт который снимает данные с датчика, записываем данные и отдаем по запросу пользователю. Результат будет выглядеть так:

../../../_images/rrdtool_example.png

Датчик температуры можно выбрать любой. Я сделал для COM порта по схеме “Электронный термометр на DS-18B20”. Стоимось вышла около 450р., без кожуха и разъемов раза в 2 дешевле.

../../../_images/rs232.png

Сам датчик температуры (DS-18B20) можно соединять параллельно с другими (до 100шт.) и он будет нормально работать на одной шине длиной до 300м. После подключения скорей всего он определится на последовательном порту /dev/ttyS0. Для снятия показателей нужна программа digitemp.

$ sudo apt-get install digitemp

Чтобы получить данные введем:

$ /usr/bin/digitemp_DS9097 -t 0 -s /dev/ttyS0 -q -o "%.2C"
-7.62

При обращении к датчику он блокируется и если в этот момент запустить еще раз команду digitemp то она вернет пустое значение. Для избежания этой ситуации напишем скрипт который по крону каждую минуту будет снимать данные и записывать в файл:

#!/bin/bash
echo `/usr/bin/digitemp_DS9097 -t 0 -s /dev/ttyS0 -q -o "%.2C"` > /projects/scripts/temp.cashe

Сейчас напишем скрипт который будет формировать rrd базу и добавлять в нее данные из temp.cashe каждую минуту.

from rrdcontroller import RRDController
import time

def update_rrd(rrd):
    f = open('temp.cashe')
    temp = f.read()
    rrd.update(temperature=temp)

    rrd.graph(period='day')
    rrd.graph(period='week')
    rrd.graph(period='month')
    rrd.graph(period='year')

if __name__ == '__main__':
    rrdfile = 'media/temperature.rrd'
    imgdir = 'media'
    rrd = RRDController(rrdfile=rrdfile, static_path = imgdir)
    rrd.create()

    while(1):
        update_rrd(rrd)
        time.sleep(60)

И файл rrdcontroller.py

rrdcontroller.py
from pyrrd.rrd import RRD, RRA, DS
from pyrrd.graph import DEF, CDEF, VDEF
from pyrrd.graph import LINE, AREA, GPRINT
from pyrrd.graph import ColorAttributes, Graph
import os, logging, time

class RRDController(object):
    def __init__(self, rrdfile, static_path):
        self.rrdfile = rrdfile
        self.static_path = static_path

    def delete(self):
        os.unlink(self.rrdfile)

    def create(self):
        if os.path.exists(self.rrdfile):
            self.rrd = RRD(self.rrdfile)
            return

        dss = []
        ds1 = DS(dsName="temperature", dsType="GAUGE", heartbeat=120,
                 minval=-10000000.0, maxval=100000000.0)
        dss.append(ds1)

        day = 24 * 60 * 60
        week = 7 * day
        month = day * 30
        quarter = month * 3
        half = 365 * day / 2
        year = 365 * day
        step = 60

        rras = []
        rra1 = RRA(cf="AVERAGE", xff=0.5, steps=1, rows=day/step)
        rra2 = RRA(cf="AVERAGE", xff=0.5, steps=30, rows=week/(30*step))
        rra3 = RRA(cf="AVERAGE", xff=0.5, steps=120, rows=month/(120*step))
        rra4 = RRA(cf="AVERAGE", xff=0.5, steps=720, rows=year/(720*step))
        rras.extend([rra1, rra2, rra3, rra4])
        rras.append(rra1)

        self.rrd = RRD(self.rrdfile, step=step, ds=dss, rra=rras)
        self.rrd.create(debug=False)
        time.sleep(2)

    def update(self, temperature):
        print temperature
        self.rrd.bufferValue(time.time(), temperature)
        self.rrd.update()

    def graph_temperature(self, period='day'):
        def1 = DEF(rrdfile=self.rrdfile, vname='temp', dsName="temperature",
                   cdef="AVERAGE")

        # TOTAL
        vdef1 = VDEF(vname='max', rpn='temp,MAXIMUM')
        vdef2 = VDEF(vname='avg', rpn='temp,AVERAGE')
        vdef3 = VDEF(vname='last', rpn='temp,LAST')
        vdef4 = VDEF(vname='min', rpn='temp,MINIMUM')

        line1 = LINE(1, defObj=def1, color='#FF2222', legend='Total')
        gprint1 = GPRINT(vdef1, "Max\\: %5.1lf %S")
        gprint2 = GPRINT(vdef2, "Avg\\: %5.1lf %S")
        gprint3 = GPRINT(vdef3, "Current\\: %5.1lf %S")
        gprint4 = GPRINT(vdef4, "Min\\: %5.1lf %S\\n")

        ca = ColorAttributes()
        ca.back = '#FFFFFF'
        ca.canvas = '#FFFFFF'
        ca.shadea = '#000000'
        ca.shadeb = '#111111'
        ca.mgrid = '#CCCCCC'
        ca.axis = '#333333'
        ca.frame = '#AAAAAA'
        ca.font = '#333333'
        ca.arrow = '#333333'

        img = "temperature-%s.png" % period
        imgname = self.static_path +"/"+ img
        start = '-1'+period

        g = Graph(imgname, imgformat='PNG', step=start,
                  vertical_label='temperature', color=ca,
                  width=700, height=150
                  )
        g.data.extend([def1, vdef1, vdef2, vdef3, vdef4, line1, gprint1,
                       gprint2, gprint3, gprint4])
        g.write()

    def graph(self, period='day'):

        self.graph_temperature(period)

После запуска скрипта он создаст RRD базу если ее еще нет, начнет каждые 60 сек добавлять значения и отрисовывать графики. Осталось только отдать эти графики по запросу. Для этого используем фреймворк Tornado.

Создадим файл weather_srv.py в той же директории где папка media с нашими графиками:

weather_srv.py
#!/usr/bin/python
#coding: utf-8
import tornado.ioloop
import tornado.web
import os

class MainHandler(tornado.web.RequestHandler):
    def get(self):

        f = open('temp.cashe')
        temp = f.read()
 self.render("index.html", temp=temp)

application = tornado.web.Application([
    (r"/", MainHandler),
    (r"/media/(.*)", tornado.web.StaticFileHandler, {"path": os.path.join(os.path.dirname(__file__), "media")}),
])

if __name__ == "__main__":
    application.listen(8009)
    tornado.ioloop.IOLoop.instance().start()

Шаблон index.html

index.html
<html>
<head>
<title>Температура на улице</title>
</head>
<body bgcolor="white" text="black">
<center><font size="5">{{ escape(temp) }}</b></font>

<img src="media/temperature-day.png" title="температура за день">
</center>
</body>
</html>
Запускаем weather_srv.py и радуемся графикам.
Описание по измерению температуры http://termal.me

Дополнительно для прочтения:


Comments

comments powered by Disqus