Electric Imp — Делаем WiFi термометр

в 7:01, , рубрики: diy или сделай сам, electric imp, google app engine, python, Raspberry Pi, метки: , , ,

Electric Imp — Делаем WiFi термометрМногие из вас знакомы с анонсом Electric Imp, который не так давно был на хабре, кроме того уже начинают появляться первые впечатления от его использования. Поскольку это устройство мне показалось перспективным и довольно интересным, я при первом появлении в продаже версии developer edition заказал себе чтобы немного поиграться и оценить возможности.

Собираем

Сам Electric Imp на момент покупки стоил $29.95, но чтобы начать им пользоваться придется в добавок приобрести плату, которую оценили в $19.95 (сейчас цена уменьшилась до $12.95).
Electric Imp — Делаем WiFi термометр

После прибытия комплекта вместо привычного “Hello, world!” моргания светодиодами решил сделать что-то более полезное. Немного пораскинув мозгами и просмотрев примеры остановился на датчике температуры. Порылся в закромах и нашел термистор 10КОм и DS18B20. С DS18B20 не сложилось так как в текущем API для Electric Imp отсутствует поддержка OneWire.
Схема подключения термистора очень простая
Electric Imp — Делаем WiFi термометр
Схема подключения термистора (источник)

Чтобы избавится от проводов, подключил аккумулятор на 750mAh, при этом на самой плате нужно джампером выставить питание от батареи. Получилось очень компактное устройство.
Electric Imp — Делаем WiFi термометр
Electric Imp — Делаем WiFi термометр
Electric Imp — Делаем WiFi термометр

Для визуального отображения температуры взял валявшийся без дела Raspberry Pi и 16х2 LCD индикатор из какого-то китайского набора.
Electric Imp — Делаем WiFi термометр
Схема подключения LCD 16x2 индикатора к Raspberry Pi (источник)

Пришлось немного помучаться потому как мне попалась какая-то промежуточная ревизия малины и код с ходу не завелся. Оказалось что на плате Raspberry Pi поменяли назначение нескольких ног, одна из них как раз нужна была для подключения LCD.

Программируем

Код для работы с термистором довольно простой, все замечательно работает и результат можно наблюдать в планере на сайте electricimp. Теперь нужно отсылать результат туда, куда мне нужно, благо в API есть возможность слать HTTP запросы, но тут есть свои нюансы. Первым делом я попытался слать результат внутри своей локальной сети, и ничего не получил. После разбора полетов выяснилось что Imp запрос шлет не непосредственно, а через свой сервер. Выхода два, или пробрасывать порт на роутере внутрь локальной сети, или слать на свой внешний сервер, а локальным сервером периодически затягивать оттуда последние показания. Проброс портов накладывает определенные ограничения на использование, поэтому я решил поднять сервер с простеньким API на App Engine, попутно заставив его рисовать графики, что оказалось очень кстати.

main.py

#!/usr/bin/env python

import webapp2
import json
import logging
import utils
import time
import os
import datetime

from google.appengine.ext.webapp import template
from google.appengine.ext import db

class Sensor(db.Model):
    temperature = db.FloatProperty(required = True)
    battery     = db.FloatProperty(required = True)
    added       = db.DateTimeProperty(auto_now_add = True, indexed=True)

class SensorRequestHandler(webapp2.RequestHandler):
    def post(self):
        data    = json.loads(self.request.body)
        params  = json.loads(data['value'])
        temp    = params['temp']
        battery = params['battery']

        sensor = Sensor(temperature = temp, battery = battery)
        sensor.put()

        self.response.out.write('OK')

    def get(self):

       sensors_data = Sensor.all().order('added').fetch(None)

        temperature_data = []
        battery_data     = []

        for item in sensors_data:
            temperature_data.append([int(time.mktime(item.added.timetuple()))*1000 ,round(item.temperature, 1)])
            battery_data.append([int(time.mktime(item.added.timetuple()))*1000, round(item.battery, 2)])
        
        path = os.path.join(os.path.dirname(__file__), 'templates/charts.html')
        self.response.out.write(template.render(path, {
            'temperature_data' : utils.GqlEncoder().encode(temperature_data), 
            'battery_data' : utils.GqlEncoder().encode(battery_data)
            }))

class LastRequestHandler(webapp2.RequestHandler):
    def get(self):
        
        ordered_list = db.GqlQuery('select * from Sensor order by added desc limit 1')
        last = ordered_list.get()

        self.response.headers['Content-Type'] = 'application/json'
        self.response.out.write(utils.GqlEncoder().encode(last))

class CleanRequestHandler(webapp2.RequestHandler):
    def get(self, bulk = 'old'):
        logging.debug("bulk: %s", bulk)
        try:
            while True:
                q = Sensor.all()

                if bulk != 'all':
                    q.filter('added <', datetime.date.today() - datetime.timedelta(days=60))
                
                assert q.count()
                db.delete(q.fetch(200))
                time.sleep(0.5)
        except Exception, e:
            self.response.out.write(repr(e)+'n')
            pass


app = webapp2.WSGIApplication([
    ('/sensor', SensorRequestHandler),
    ('/sensor/last', LastRequestHandler),
    ('/sensor/clean/?(all)?', CleanRequestHandler)
], debug=True)

В итоге результат выглядит так:
— Imp раз в 10 мин шлет на внешний сервер показания температуры
— Сервер сохраняет это значение
— Raspberry Pi раз в несколько минут подтягивает показания температуры и отображает ее на LCD дисплее.
Параллельно с температурой решил собирать показания напряжения батареи, но оказалось что функция из API возвращает напряжение с самой платы (~ 3.25В).
К сожалению текущие возможности выполнения HTTP запроса сильно ограничены, поэтому пришлось извратиться и паковать данные в JSON, который в свою очередь второй раз пакуется в JSON уже внутри HTTPRequest ноды.

class Termistor
{
    pin_num = null;
    
    constructor(pin){
        pin_num = pin
        hardware["pin" + pin_num].configure(ANALOG_IN);
    }
    
    function read(){
        return hardware["pin" + pin_num].read();
    }
    
    function getTemperature(){
        local temp = math.log(((655350000/read()) - 10000));
        temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * temp * temp ))* temp );
        temp = temp - 273.15;
        return temp;
    }
}

local sensor = Termistor(9);

local output = OutputPort("Temperature", "string");

imp.configure("Termistor 10K", [], [output]);

function capture(){
    imp.wakeup(600.0, capture);
    local temp = sensor.getTemperature();
    local jsonOut = "{"temp":"+temp+", "battery":"+hardware.voltage()+"}";
    output.set(jsonOut);
    server.show(format("%1.1fºC", temp));    
}

capture();

imp.sleep(2.0)

server.sleepfor(600.0)

Пожинаем плоды

Графики стабильно строятся, выглядят так:
Electric Imp — Делаем WiFi термометр

Raspberry Pi выводит температуру
Electric Imp — Делаем WiFi термометр

Для более наглядной демонстрации снял небольшое видео (рекомендую смотреть в 720p и во весь экран):

Энергосбережение

Imp может запускаться в трех режимах

Отключенный powersave mode (по умолчанию)

В этом режиме WiFi модуль всегда включен. Это способствует низким сетевым зажержкам но приводит к высокому потреблению тока ~60-80мА для 3.3В

Включенный powersave mode

В данном режиме потребление падает до 5мА в моменты, когда нет передачи по WiFi, но соответственно увеличивается задержка перед передачей и она может быть больше 250мс.

Глубокий сон

В этом режиме Imp отключается от WiFi сети, перестает выполнять код, теряет контекст (кроме данных в специальной nv таблице) и переходит в режим очень низкого электропотребления (~6мкА). Вывести из сна может либо предварительно взведенный таймер, или wake-up пин. Старт и подключение к WiFi сети занимает около 2с.

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

750/24*3 ~ 10 мА, что соответствует включенному powersave режиму.

В режиме глубокого сна в качестве датчика температуры Imp трудится уже месяц, и напряжение упало с 4.2 до 3.68, то есть LiPo батареи на 750mAh должно хватить приблизительно на полтора месяца, что не может не радовать.

Планы

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

Вывод

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

Полезные ссылки

1. Исходный код на github
1. Arduino + termistor
2. Electric Imp Wiki
3. Drive a 16x2 LCD with the Raspberry Pi

Автор: blo

Источник


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js