Школьный звонок на Raspberry Pi с удаленным управлением

    Доброго времени суток, уважаемые хабровчане. Не секрет, что одноплатные Linux компьютеры на базе SoC на сегодняшний день получили широкое распространение как среди любителей, так и среди более-менее профессиональных пользователей. Все больше и больше задач можно решить с помощью микрокомпьютеров, и даже тех задач, которые раньше решались исключительно при помощи микроконтроллеров. Казалось бы, использование полноценного, хоть и мелкого компьютера для решения простых задач — это тот еще оверкилл, однако давайте разберемся, так ли это плохо? Эта статья является ответом на наш небольшой спор с хабровчанином devzona по этому поводу.

    Предыстория


    Казалось бы, что может быть более явной нишей для применения микроконтроллеров, чем автоматизация школьного звонка? Именно так думал неизвестный разработчик лет 5-7 назад, когда собирал вот такое замечательное устройство.

    Собрано, судя по всему, на МК серии 8050, имеет на борту часики реального времени, умеет это время показывать на самодельной светодиодной матрице, и самое главное, умеет вовремя дергать релюшку, включающую школьный звонок. Устройство благополучно работает уже много лет, претензий к нему не было. Однако, все течет и меняется, и однажды простая Харьковская школа с углубленным изучением чего-то там решила пройти переаттестацию в лицей с еще более углубленным изучением того самого. Такая переаттестация, помимо всего прочего, требует перехода с 45-минутных уроков на пары, состоящие из двух академических часов по 40 минут. Тут-то и пришла беда. Разработчик часиков на МК благополучно спился уехал за границу, исходников не оставил, возможности перенастройки не предусмотрел. Именно с этой проблемой постучался ко мне в Скайп одним осенним днем мой друг Костя.

    Осмотрев пациента пришло понимание, что быстрее чем за пару недель его переделать под требования заказчика не получится. По сути, нужно переписывать код с нуля. И, внезапно, вечером этого же дня курьер из DHL привез мне очередной Raspberry. Тут и пришла идея сделать свои часики, да не просто часики, а с магией. Ведь у нас есть целый микрокомпьютер с полноценным линуксом не борту, руки развязаны, возможности безграничны!

    Постановка задачи


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

    Для реализации проекта нам нужно следующее:

    • Демон, умеющий дергать нужную GPIO ножку в нужное время
    • Веб-интерфейс для конфигурирования времени звонков
    • Часы реального времени
    • Силовая электроника для управления школьными звонками


    Я преднамеренно упускаю начальную конфигурацию Raspberry Pi, интернет полон материалами по установке дистрибутива, настройке сети, тайм-зоны и т.д.

    Итак, приступим.

    Часы реального времени


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



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

    По правильному, часики должны обновляться в случае успешной синхронизации времени малинки с NTP сервером, и, если доступа к NTP серверу нет, тогда системные часы малинки должны быть синхронизированы с часами реального времени. Такой алгоритм необходим, так как DS1302 имеет привычку уползать на пару секунд в сутки, что неприятно. Однако, как заставить ntpd запускать скрипт после успешной синхронизации, я так и не нашел. Поэтому родился такой вот костыль:

    /usr/local/bin/update_rtc
    #!/bin/bash
    
    LOG="/var/log/rtc-sync.log"
    DATE=`date`
    
    sleep 30
    
    echo "*** $DATE" >>$LOG
    
    until ping -nq -c3 8.8.8.8; do
        echo "No network, updating system clock from RTC." >>$LOG
        rtc-pi 2>&1
        exit
    done
    
    echo "Network detected. Updating RTC." >>$LOG
    date +%Y%m%d%H%M%S |xargs ./rtc-pi 2>&1
    


    /etc/init.d/rtc
    #!/bin/sh
    # /etc/init.d/rtc 
    
    ### BEGIN INIT INFO
    # Provides:          RTC controll
    # Required-Start:    $remote_fs $syslog
    # Required-Stop:     $remote_fs $syslog
    # Default-Start:     2 3 4 5
    # Default-Stop:      0 1 6
    # Short-Description: Simple script to start RTC sync
    # Description:       A simple script from prostosergik <serge.liskovsky@gmail.com> which will run script that synchronizes RTC module clock with system clock at startup.
    ### END INIT INFO
    
    
    case "$1" in
      start)
        echo "RTC sync..."
        /usr/local/bin/update_rtc& 2>&1
        ;;
      stop)
        echo "Stopping RTC Sync..."
        # kill application you want to stop
        killall update_rtc
        ;;
      *)
        echo "Usage: /etc/init.d/rtc {start|stop}"
        exit 1
        ;;
    esac
    
    exit 0
    


    … и активируем автозагрузку:
    sudo update-rc.d rtc defaults


    Эти два файла позволяют синхронизировать системные часики малинки с RTC в случае, если после загрузки не обнаружена сеть, или обновить время в RTC, если сеть обнаружена. Через 30 сек после загрузки ntpd должен бы уже успеть обновить системные часы. В худшем случае, в RTC будет записано последнее время, когда Raspberry был включен. Я знаю, что это решение далеко не идеальное, но лучшего придумать не смог. Единственное, что приходит в голову — добавить строчку в крон для обновления RTC раз в 2-3 часа, дабы быть уверенным, что в часах реального времени более-менее точные данные. Если многоуважаемое сообщество подскажет лучшее решение — буду только рад.

    Веб-сервер


    Тут долго думать не пришлось. Основная задача сервера — показывать две странички и обрабатывать один POST запрос. Хрестоматийная реализация веб-сервера на Python просто напрашивается сама собой.

    webserver.py
    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    
    import cgi, re, json
    from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
    import collections
    
    from config import * 
    
    class MainRequestHandler(BaseHTTPRequestHandler):
    
        def do_GET(self):
    
            if self.path == '/':
                
                lessons = readSchedule()
                schedule = ''
                for lesson in lessons:
                    schedule += u"<b>Час "+lesson+"</b>: "+lessons[lesson].get('start', '--:--') + " - " + lessons[lesson].get('end', '--:--') + "<br />"
                
                data = {
                    'schedule': schedule.encode('utf-8')
                }
                
                TemplateOut(self, 'index.html', data)
                return
    
            elif self.path == '/form.html':
                
                lessons = readSchedule()
    
                form = ''
                for lesson in lessons:
                    form += u"<div class='form_block'><label>Час "+lesson+"</label> <input type='text' name='lesson_"+lesson+"_start' value='"+lessons[lesson].get('start', '--:--') + "'> - <input type='text' name='lesson_"+lesson+"_end' value='"+lessons[lesson].get('end', '--:--') + "'> </div> """
    
                data = {
                    'form': form.encode('utf-8')
                }
                
                TemplateOut(self, 'form.html', data)
                return
            
            elif self.path == '/remote.html':
                
                lessons = readScheduleRemote()
    
                form = ''
                for lesson in lessons:
                    form += u"<div class='form_block'><label>Час "+lesson+"</label> <input type='text' name='lesson_"+lesson+"_start' value='"+lessons[lesson].get('start', '--:--') + "'> - <input type='text' name='lesson_"+lesson+"_end' value='"+lessons[lesson].get('end', '--:--') + "'> </div> """
    
                data = {
                    'form': form.encode('utf-8')
                }
                
                TemplateOut(self, 'form.html', data)
                return
    
            else:
                try:
                    TemplateOut(self, self.path)
                except IOError:
                    self.send_error(404, 'File Not Found: %s' % self.path)
    
        def do_POST(self):
    
            # Parse the form data posted
            form = cgi.FieldStorage(
                fp=self.rfile, 
                headers=self.headers,
                environ={
                    'REQUEST_METHOD':'POST',
                    'CONTENT_TYPE':self.headers['Content-Type'],
                }
            )
    
            lessons = {}
            if self.path.endswith('save'):
    
                # Echo back information about what was posted in the form
                for field in form.keys():
    
                    field_item = form[field]
    
                    if type(field_item) == type([]):
                        pass # no arrays processing now
                    else:
                        if field_item.filename:
                            pass #no files now.
                        else:
                            if re.match('lesson_([\d]+)_(start|end)', field):
                                (lesson, state) = re.findall('lesson_([\d]+)_(start|end)', field)[0]
                                try:
                                    lessons[lesson]
                                except Exception:
                                    lessons[lesson] = {}
    
                                lessons[lesson][state] = field_item.value
    
                # printlessons
                json_s = json.dumps(lessons)
    
                if json_s:
                    try:
                        f = open(JSON_FILE, 'w+') 
                        f.write(json_s)
                        f.close()
    
                        HTMLOut(self, 'Saved OK.' + JS_REDIRECT)
                    except IOError, e:
                        # raise e
                        HTMLOut(self, 'Error saving. IO error. '+e.message)
                else:
                    HTMLOut(self, 'Json Error.')
            else:
                self.send_error(404, 'Wrong POST url: %s' % self.path)
    
            return
    
    
    
    def Redirect(request, location):
        request.send_response(301)
        request.send_header('Location', location)
        request.end_headers()           
        return     
    
    def Headers200(request):
        request.send_response(200)
        request.send_header('Content-type',    'text/html')
        request.end_headers()         
        return   
    
    def TemplateOut(request, out_file, data = {}):
    
        f = open(SCRIPT_DIR + out_file) 
        out = f.read()
        f.close()
    
        #tiny template engine
        for key, var in data.items():
            out = out.replace("{{"+key+"}}", var)
    
        HTMLOut(request, out)
    
    def HTMLOut(request, html):
        Headers200(request)
    
        f = open(SCRIPT_DIR + 'base.html') 
        out = f.read()
        f.close()
    
        out = out.replace("{{content}}", html)
        request.wfile.write(out)
    
    def readSchedule():
    
        try:
            f = open(JSON_FILE, 'r') 
            json_s = f.read()
            f.close()
        except IOError:
            return [] 
        
        try:
            lessons = json.loads(json_s)
        except Exception:
            return []
    
        lessons = collections.OrderedDict(sorted(lessons.items()))
    
        return lessons
    
    def readScheduleRemote():
    
        import urllib2
    
        try:
            response = urllib2.urlopen(REMOTE_URL)
            json_s = response.read()    
        except Exception:
            return []
    
        try:
            lessons = json.loads(json_s)
        except Exception:
            return []
    
        lessons = collections.OrderedDict(sorted(lessons.items()))
    
        return lessons
    
    def main():
        try:
            server = HTTPServer(('', 8088), MainRequestHandler)
            print 'Started httpserver...'
            server.serve_forever()
        except KeyboardInterrupt:
            print '^C received, shutting down server.'
            server.socket.close()
    
    if __name__ == '__main__':
        main()
    
    


    Из скуки и для лучшей расширяемости был даже добавлен простенький шаблонизатор. Обратите внимание, интерпретатор прописан в начале скрипта, так что после установки прав на исполнение, скрипт может быть запущен прямо из командной строки.

    Что делает этот скрипт, думаю, понятно и без комментариев. Обработчик GET-запросов попросту отдает клиенту две формочки и главную страницу, заполняя переменную данными про текущее расписание. Обработчик POST-запросов сохраняет данные из формы в JSON-файл, который и является базой звонков.

    Собственно, управлятор школьным звонком


    Благодаря замечательной библиотеке GPIO для Python, моргать светодиодиком школьным звонком с малинки очень просто. Этим занимается такой скрипт:

    daemon.py
    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    
    import time
    import threading
    import json
    
    import RPi.GPIO as GPIO
    
    from config import * 
    
    GPIO.setmode(GPIO.BCM)
    GPIO.setwarnings(False)
    
    GPIO.setup(25, GPIO.OUT)
    GPIO.output(25, False)
    
    def read_schedule():
        schedule = []
        try:
            f = open(JSON_FILE, 'r') 
            json_s = f.read()
            f.close()
            try:
                json_data = json.loads(json_s)
            except Exception, e:
                json_data = []
    
            for lesson in json_data.values():
                start = lesson.get('start', False)
                end = lesson.get('end', False)
                if start is not False:
                    # print start.split(":")
                    (s_h, s_m) = start.split(":")
                    schedule.append({'h': int(s_h), 'm':int(s_m)})
                    del s_h
                    del s_m
                if end is not False:
                    (e_h, e_m) = end.split(":")
                    schedule.append({'h': int(e_h), 'm':int(e_m)})            
                    del e_h
                    del e_m
    
            return schedule
    
            # schedule 
        except IOError, e:
            return []
        except Exception, e:
            return []
    
    class Alarm(threading.Thread):
        def __init__(self):
            super(Alarm, self).__init__()
            self.schedule = read_schedule()
            self.keep_running = True
    
        def run(self):
            try:
                while self.keep_running:
                    now = time.localtime()
    
                    for schedule_item in self.schedule:
                        if now.tm_hour == schedule_item['h'] and now.tm_min == schedule_item['m']:
                            
                            print "Ring start..."
                            GPIO.output(25, True)
                            
                            time.sleep(5)
                            
                            print "Ring end..."
                            GPIO.output(25, False)
    
                            self.schedule = read_schedule() #reload schedule if it was changed
                            time.sleep(55) # more than 1 minute                        
    
                    #print "Check at "+str(now.tm_hour)+':'+str(now.tm_min)+':'+str(now.tm_sec) 
    
                    time.sleep(1)
            except Exception, e:
                raise e
                # return
        def die(self):
            self.keep_running = False
    
    alarm = Alarm()
    
    def main():
        try:
            alarm.start()
            print 'Started daemon...'
            while True:
                continue
        except KeyboardInterrupt:
            print '^C received, shutting down daemon.'
            alarm.die()
    
    if __name__ == '__main__':
        main()
    
    


    Скрипт создает новый поток, в котором проверяет время каждую секунду. Если время найдено в файле расписания, то на 5 секунд включаем звонок (подаем высокий уровень на ножку 25 GPIO). После каждого звонка перечитываем расписание, на случай, если оно было изменено из веб-интерфейса. Все прозрачно и просто.

    Демонизируем и дрессируем смотрового пса


    Действуя по аналогии с автозапуском синхронизации RTC, создаем следующие файлики:

    /etc/init.d/schedule_daemon
    #!/bin/sh
     
    ### BEGIN INIT INFO
    # Provides:          schedule_daemon
    # Required-Start:    $remote_fs $syslog
    # Required-Stop:     $remote_fs $syslog
    # Default-Start:     2 3 4 5
    # Default-Stop:      0 1 6
    # description: School Ring Schedule daemon
    # processname: School Ring Schedule daemon
    ### END INIT INFO
    
    export SCHEDULE_ROOT=/home/pi/ring_app
    export PATH=$PATH:$SCHEDULE_ROOT
    
    SERVICE_PID=`ps -ef | grep daemon.py | grep -v grep | awk 'END{print $2}'`
     
    usage() {
            echo "service schedule_daemon {start|stop|status}"
            exit 0
    }
    
    case $1 in
     
        start) 
            if [ $SERVICE_PID ];then
                echo "Service is already running. PID: $SERVICE_PID"
            else
                $SCHEDULE_ROOT/daemon.py& 2>&1
            fi
            ;;
        stop) 
            if [ $SERVICE_PID ];then
                kill -9 $SERVICE_PID
            else
                echo "Service is not running"
            fi
            ;;
        status) 
    
            if [ $SERVICE_PID ];then
                echo "Running. PID: $SERVICE_PID"
            else
                echo "Not running"
            fi
            ;;
        *) usage
            ;;
    esac
    


    /etc/init.d/schedule_webserver
    #!/bin/sh
     
    ### BEGIN INIT INFO
    # Provides:          schedule_webserver
    # Required-Start:    $remote_fs $syslog
    # Required-Stop:     $remote_fs $syslog
    # Default-Start:     2 3 4 5
    # Default-Stop:      0 1 6
    # description: School Ring Schedule web-server
    # processname: School Ring Schedule web-server
    ### END INIT INFO
    
    export SCHEDULE_ROOT=/home/pi/ring_app
    export PATH=$PATH:$SCHEDULE_ROOT
    
    SERVICE_PID=`ps -ef | grep webserver.py | grep -v grep | awk 'END{print $2}'`
     
    usage() {
            echo "service schedule_webserver {start|stop|status}"
            exit 0
    }
    
    case $1 in
     
        start) 
            if [ $SERVICE_PID ];then
                echo "Service is already running. PID: $SERVICE_PID"
            else
                $SCHEDULE_ROOT/webserver.py& 2>&1
            fi
            ;;
        stop) 
            if [ $SERVICE_PID ];then
                kill -9 $SERVICE_PID
            else
                echo "Service is not running"
            fi
            ;;
        status) 
            if [ $SERVICE_PID ];then
                echo "Running. PID: $SERVICE_PID"
            else
                echo "Not running"
            fi
            ;;
        *) usage
            ;;
    esac
    



    И скрипты «сторожевых собачек» для них. Эти скрипты проверяют, запущен ли сервис, и, при необходимости, запускают его.

    /etc/init.d/schedule_daemon_wd
    #!/bin/sh
     
    ### BEGIN INIT INFO
    # Provides:          schedule_daemon_wd
    # Required-Start:    $remote_fs $syslog
    # Required-Stop:     $remote_fs $syslog
    # Default-Start:     2 3 4 5
    # Default-Stop:      0 1 6
    # description: School Ring Schedule daemon watchdog
    # processname: School Ring Schedule daemon watchdog
    ### END INIT INFO
    
    export SCHEDULE_ROOT=/home/pi/ring_app
    export PATH=$PATH:$SCHEDULE_ROOT
    
    SERVICE_PID=`ps -ef | grep daemon.py | grep -v grep | awk '{print $2}'`
     
    check_service() {
            if [ -z $SERVICE_PID ];then
                service schedule_daemon start
            fi
    }
     
    check_service
    
    usage() {
        echo "schedule_daemon_wd {start|stop|status}"
        exit 0
    }
     
    case $1 in
        start ) 
    
            if [ $SERVICE_PID ];then
                echo "schedule_daemon is already running. PID: $SERVICE_PID"
            else
                service schedule_daemon start
            fi
            ;;
        stop ) 
    
            if [ $SERVICE_PID ];then
                service schedule_daemon stop
            else
                echo "schedule_daemon is already stopped"
            fi
            ;;
        status) 
            if [ $SERVICE_PID ];then
                echo "schedule_daemon is running. PID: $SERVICE_PID"
            else
                echo "schedule_daemon is not running"
            fi
            ;;
        *) usage
            ;;
    esac
    


    /etc/init.d/schedule_webserver_wd
    #!/bin/sh
     
    ### BEGIN INIT INFO
    # Provides:          schedule_webserver_wd
    # Required-Start:    $remote_fs $syslog
    # Required-Stop:     $remote_fs $syslog
    # Default-Start:     2 3 4 5
    # Default-Stop:      0 1 6
    # description: School Ring Schedule web-server watchdog
    # processname: School Ring Schedule web-server watchdog
    ### END INIT INFO
    
    export SCHEDULE_ROOT=/home/pi/ring_app
    export PATH=$PATH:$SCHEDULE_ROOT
    
    SERVICE_PID=`ps -ef | grep webserver.py | grep -v grep | awk '{print $2}'`
     
    check_service() {
            if [ -z $SERVICE_PID ];then
                service schedule_webserver start
            fi
    }
     
    check_service
    
    usage() {
        echo "schedule_webserver_wd {start|stop|status}"
        exit 0
    }
     
    case $1 in
        start ) 
    
            if [ $SERVICE_PID ];then
                echo "schedule_webserver is already running. PID: $SERVICE_PID"
            else
                service schedule_webserver start
            fi
            ;;
        stop ) 
    
            if [ $SERVICE_PID ];then
                service schedule_webserver stop
            else
                echo "schedule_webserver is already stopped"
            fi
            ;;
        status) 
    
            if [ $SERVICE_PID ];then
                echo "schedule_webserver is running. PID: $SERVICE_PID"
            else
                echo "schedule_webserver is not running"
            fi
            ;;
        *) usage
            ;;
    esac
    


    Аналогично, делаем эти скрипты автоматически загружаемыми при старте системы:

    sudo update-rc.d schedule_daemon_wd defaults
    sudo update-rc.d schedule_webserver_wd defaults
    

    И добавляем в крон новые задания:

    /etc/cron.d/wd.cron
    #Watchdog tasks 
    
    * * * * * /etc/init.d/schedule_daemon_wd
    * * * * * /etc/init.d/schedule_webserver_wd
    
    


    Теперь мы можем быть уверены, что оба демона запустились и будут стабильно работать. Не забываем добавить новую строчку в конце wd.cron, иначе crond будет его игнорировать!

    Немного про силовую электронику


    Вся силовая часть собрана совершенно стандартно. Суммарная мощность звонков в школе около 0.5 КВт, так что симистора BC137X в паре с оптроном MOC3061 вполне достаточно для коммутации этого хозяйства. Как показала практика, 3.3 вольта логической единицы достаточно для уверенного включения оптрона.


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

    Чего не хватает


    Конечно, имея полноценный Linux-компьютер в своем распоряжении, можно «наворачивать» функциональность до бесконечности, причем времени на разработку уйдет сравнительно мало. Именно это обстоятельство говорит в пользу применения микрокомпьютеров для решения задач, с которыми, казалось бы, справится и микроконтроллер. Однако, все-же перечислю то, чего, по моему мнению, не хватает текущей реализации:

    Во первых, безопасность. Стоило бы заморочиться на простой HTTP-Auth хотя бы, или, дописав немного скрипт, сделать базу паролей для входа в «админ-панель» системы. Да и над фильтрацией данных поработать стоит, как до, так и после отправки формы.
    Во вторых, нужно бы добавить добавление/удаление академ. часов в форму. Внимательный читатель заметил, что это можно сделать попросту дорисовав в форму на клиентской стороне необходимые поля при помощи, например, простенького JavaScript кода.
    В третьих, мне так хотелось сделать «тревожную кнопку» на главной, которая запускала бы звонок за 5-10 секунд. Пусть это будет маленькая задачка для пытливых умов читателей, благо, все необходимое для этого есть в статье.
    В четвертых, не хватает блока бесперебойного питания. Ввиду отказа заказчика от разработки, до него мы так и не дошли.

    Чем всё закончилось


    К сожалению, Харьковская гимназия с углубленным изучением чего-то там решила, что собрать по 3 гривны с каждого родителя это очень, очень трудно, и нам в итоге дали от ворот поворот, поэтому реализация остановилась на действующем прототипе, который не содержит некоторых важных для конечной системы элементов. Но время, потраченное на разработку не прошло даром. Опыт разработки приложений для работы с железом на Python мне, надеюсь, не раз пригодится в жизни, тем более на загородном участке заканчивается строительство дома, в котором предусмотрена возможность управления всем из единого мозгового центра. Если смог управлять звонком, то и лампочки по расписанию включать смогу.

    Послесловие


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

    На реализацию всего вышеописанного было потрачено чуть более трех часов. Доведение до ума того, что есть требует еще столько же. Традиционно попрошу не пинать сильно за кривоватый местами код и возможные ошибки. Это моя первая статья на Хабре, и первый реализованный проект на Python. Всегда рад поправкам, пожеланиям и предложениям. Скриншоты и видео работы выложу по требованию.

    С нетерпением жду реализации комрадом devzona подобного функционала, но только на основе Arduino. Уверен, мне есть чему у него поучиться в плане разработки устройств на микроконтроллерах. Статья обещает быть воистину захватывающей.
    Поделиться публикацией

    Похожие публикации

    Комментарии 57
      +23
      компьютер для дерганья релюшки звонка?? серъезно… так давайте уже i3 ну или что уж там i7…
        +8
        и нужно сделать дублирование для надежности, а то вдруг отказ системы
          0
          Вы не поверите, я примерно аналогичную задачу на виртурилке делал :-D Тупо три релюшки дёргал. Я знаю что можно проще и дешевле, но, блин, надо было шылд с релюшками проверить, вот и совместил приятное с полезным. Зато вайфай, веб-интерфейс, все дела :-D Так что если я и сам раньше, бывало, критиковал методику «из пушки по воробьям», то теперь не отрицаю — иногда такое очень даже вполне имеет право на жизнь.
            +2
            Понимаю, что предисловие обычно никто не читает и по ссылкам не ходят, а статья была ответом на утверждение devzona о том, что на Ардуине, подчеркиваю, на Ардуине с бутербродом шилдов такой точно функционал сделать быстрее и проще. Мне все интересно, так оно или нет.

            И если за 3 часа сделать рабочее устройство, решающее проблему здесь и сейчас, вместо нескольких дней на покупку, доставку, разработку и отладку девайса на ардуине — это совсем плохо, то не знаю…

            ИМХО, Распбери относительно Ардуины это то же, что Ардуина относительно голого МК. Т.е. еще одна ступенька абстракции.
              0
              Прочитал весь топик, про «наш ответ Керзону» тож прочитал. Ничего против не имею, сам грешен :-) Кому-то нравится голое железо паять-лудить, а кому-то результат быстро нужен и с минимумом телодвижений.
                0
                Тут — именно второй случай =)
            +1
            Передерну: «Ардуино? Чтобы моргать елочной гирляндой? Вместо таймера на NE555?»

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

            Применение 4-х процессорных смартфонов с гигом памяти для чятика вконтактике и просмотра котиков никого не удивляет, а Распбери, созданный для работы с железом из-под Линуха при помощи скриптов на Пайтоне, это, несомненно, нонсенс.
              0
              да какая выгода ставить на звонок малину если даже можно ту самую ардуинку, которая гораздо дешеле… маркетинг?, а ну да «Только сегодня купите школьный звонок работающий на Ubuntu, частота процессора 700МГц — теперь вы забудите о зависаниях и звонков с опаздыванием на 5 минут, оперативной паняти на выбор 256 или 512 Мб, чего Вам хватит на 3! различных графика звонков.»
                +1
                Скорость разработки, и легкость поддержки, например.

                Как Ардуина была сделана для понижения порога вхождения в разработку на МК — и посмотрите к чему это привело! Десятки тысяч людей открыли для себя мир программируемых контроллеров. — так и Малинка сделана для понижения порога вхождения в этот ваш ембедед. И таки продается миллионными тиражами.

                Раньше программы писали в машинных кодах. Сейчас — для отрисовки формы на 3 кнопки требуется .NET фреймверк, или, если про линух, тот же GTK+, так и для решения аппаратных задач можно использовать что-то более менее сложное, благо цена позволяет. А в итоге, конечное устройство может выйти даже дешевле, т.к. время на разработку сокращается в разы.

                Под маркетингом я понимаю не громкие крики реклам, кстати. Маркетинг — наука про рынок. Дешево разработать, дорого продать, заработать на поддержке. И т.д.
                  +2
                  <offtopic>Мне кажется, что вероятность зависания компа с Ubuntu гораздо выше, чем зависания топорной платы на топорном микроконтроллере ;)</offtopic>
                    0
                    Если там Распбиан (а так оно, по-моему, и должно быть) — то при реально топорной плате с топорным МК как раз-таки вероятность зависания этого самого МК на порядок выше.
                      0
                      It depends. Особенно, если железная часть сделана без нарушений.

                      Тогда всё зависит от «криворукости» программиста. При не сильно большой вероятности ошибки на единицу объема кода, в огромном стеке (имея ввиду debian) кода на порядки больше, чем в самом application (где вероятность ошибки на строчку существенно выше).

                      Но wdt никто не отменял.
                +2
                У нас в университет стоит компьютер, с WinXP. И на нем, по расписанию включается звонок.
                Иногда с утра по всему кампусу раздается звук включения WinXP…
              • НЛО прилетело и опубликовало эту надпись здесь
                  +5
                  Тут вся фишка в управлении расписанием с этой штуки. Собственно, заказали его потому, что поменять старый управлятор не представлялось возможным, никак. Школа, а теперь уже лицей, сейчас меняет расписание довольно часто. Удобно это делать из веб-мордочки.
                  0
                  Гм. Не кажется ли Вам, что так называемые вотчдоги мало того, что избыточные, так ещё и просто не работают? Для управления демонами есть специальные инструменты, выбросьте эти нерабочие костыли.

                  Более того, Ваш скрипт обновления RTC сильно уменьшает пользу от этого самого RTC в случае отсутствия сети. В случае, если за 30 секунд время не будет синхронизировано, вы перезаписываете правильное время в RTC неправильным из системных часов.

                  Кстати, по умолчанию каждый час специальный скрипт сбрасывает системное время в файл, а при загрузке оно оттуда подтягивается.
                    0
                    Наоборот. Если 30 секунд сети нет, то системное время перезаписывается из RTC.

                    По поводу вотчдогов, подскажите лучше, с радостью ознакомлюсь.
                      0
                      А Вы вчитайтесь. Представьте, сеть есть, а вот синхронизация по какой-то причине не произошла. Такое бывает, да. Всё, пропало время.
                        0
                        Согласен =) Вот если бы ntpd умел бы вызывать скрипт после удачной синхронизации, тогда да. Было бы красиво и просто. Но он, похоже, не умеет.
                          0
                          ntpd непрерывно и плавно перестраивает время (хотя, некоторые помнят leap second'13).

                          Если допустимы резкие переподстройки — можно использовать ntpdate (работает и при большой разнице часов).
                            0
                            Да, похоже таки я не очень разобрался в этом вопросе. Попробую переделать для работы с ntpdate.
                            +1
                            Похоже, Вы не совсем понимаете, как работает ntpd. Он не единократно синхронизирует системные часы, а постоянно подстраивает скорость их хода.
                            +1
                            Кстати, классический пример: при наличии сети и значительной разницы времени между локальными часами и NTP синхронизация не произойдёт.
                            0
                            Насчёт скриптов-костылей. Расскажите, что делает Ваш скрипт schedule_daemon_wd, если запустить его с параметром stop?
                              0
                              Supervisor весьма неплох, и есть в репозиториях Debian. Умеет логгировать вывод, запускать при старте и перезапускать при смерти. И веб-морда есть.
                                0
                                Спасибо, поиграюсь с ним.
                                  0
                                  а еще есть гораздо более простое решение чем supervisord, которое тоже есть в репках дебиана — runit. Веб-морда тоже имеется — ruby gem runit-man.
                            0
                            На RPi случайно нет watchdog timer'а? Если система работает 24х7 — полезное дело, чтобы автоматически перезагрузить железку при зависании ядра.
                              0
                              Можно и на TP-LINK wr703n такое сварганить ))
                                0
                                Думал =) Обвеса много вокруг него. И, как ни странно, это дороже, чем на Малинке.
                                +2
                                Применение микрокомпьютеров для тривиальных, казалось бы, задач может поднять их реализацию на принципиально новый уровень, и вместо простейших реализаций...

                                Бывают случаи, когда достаточно простой реализации, что в разы упростит дальнейшую поддержку. Есть задача. ее нужно решить максимально просто и надежно, и не стоит ради дергания одной релюшки по таймеру делать серверную, так как это «делает систему гибкой» и если что можно будет запустить свой вконтактик, а то мало ли понадобится…
                                  +1
                                  Несомненно. Но в наше время уже микро-сд карточки для шаринга фото — это отдельный компьютер. Выключатель лампочки — отдельный компьютер. Скоро наступит время, когда единицей измерения вычислительной техники будет микрокомпьютер. как когда-то реле, потом лампа, транзистор, микросборка, микросхема, микроконтроллер… пришло время микрокомпьютеров. Распбери сам по себе великоват. Есть множество еще более мелких — размером с коробок спичек — компьютеров с низкой ценой именно для таких целей.

                                  Я рассматриваю микрокомпьютеры с линуксом как аналог Фреймверков. Можно писать ан ассемблере, а можно подключить фреймверк. Думаю, аналогия ясна.
                                  0
                                  Поздравляю с первым постом. А какой номер школы?
                                    0
                                    Давай в приват, не публичный вопрос.
                                    +1
                                    1) Есть web.py. Рекомендую — и не придётся свой сервер писать, и работает отлично, и плюшек достаточно =)
                                    2) У Raspberry Pi есть watchdog, как grossws подсказывает. Советую использовать — инструкция есть тут.
                                    3) Также рекомендую такую штуку:
                                    pypi.python.org/pypi/python-daemon/

                                    А так Ваша система отлично выглядит =) Ждём ответа devzona.
                                      0
                                      1) Да, знаю такую штуку, но не пользовал из соображений минимализма и желания поковыряться в кишочках =)
                                      2) Вотчдог, если я не ошибаюсь, там на зависание ядра. А это на отлаженной системе весьма редкое явление. В контексте — не вижу смысла.
                                      3) Чем он принципиально лучше кроме рекламного описания? =)
                                        0
                                        1) Ну у web.py как раз ничего особо лишнего нет, хоть штука и зело мощная в умелых руках (не моих кривоватых) =) А так да — понимаю Ваше стремление ;-)
                                        2) Ну раз в год и палка стреляет =) Мало ли, один раз зависнет и вовремя не перезагрузится — а у заказчика уже впечатление негативное есть, что у устройства могут возникнуть проблемы, приводящие к отказу, и нет автоматического средства решения этих проблем. Так что это просто полезная функция для подстраховки устройства, работающего 24/7, хоть для этого не особо и предназначенного.
                                        3) Ну если задача — написать пару велосипедов во имя обучения и фана, то разница не важна =) А если экономить время, то это готовая лежащая на блюдечке в pypi библиотека, отлаженная и многофункциональная, плюс довольно популярная. Плюсы в практике перед велосипедом очевидны =)
                                          0
                                          [offtopic]Ну чёрт, каждый ответ начинается с «ну».[/offtopic]
                                            0
                                            1) Согласен. Можно и Джангу прикрутить, но не нужно, да. =)
                                            2) Уговорили, на будущее учту. Боевую малинку ждет еще куча применений!
                                            3) Собственно, цель научиться и ставилась. По сути, разницы никакой, кроме описанной вами.
                                        +9
                                        К сожалению, Харьковская гимназия с углубленным изучением чего-то там решила, что собрать по 3 гривны с каждого родителя это очень, очень трудно, и нам в итоге дали от ворот поворот, поэтому реализация остановилась на действующем прототипе, который не содержит некоторых важных для конечной системы элементов.

                                        1. Школа законодательно не имеет никакого права собирать по 3 гривны на нужды. Она обеспечивается из бюджета и Гороно за этим следит с метровой дилдой на изготовку. Да и вообще любая украинская школа не имеет права ничего собирать вообще. Даже если собирают — то оформляют это как «благодарные родители от сногшибательных знаний собрались и сделали директору ремонт»

                                        2. Школа в заведомо худшем положении, и я не думаю, что ваше меценатство, случись такое, не имело бы положительной оценки в будущем. В конце концов — поправили бы себе карму. Да и у харьковских IT-шников сейчас расцвет, RPi стоит дешевле похода в жирного гуся — why not? Не понимат.

                                        3. Любое собранное устройство должно работать по назначению. И то, что вы его собрали, и оно лежит на полке — лично ваш минус. Поставив его в школе и наладив рекламу мелкосерийки на примере этой школы вы спокойно могли бы перехватить инициативу…

                                        Вывод: Зажали потенциально хорошую железку, погнавшись за прямой прибылью. Сами в луже, железка пылится, школа думает, как брать в штат «звонаря», тлен, даградация.

                                        P.S.
                                        Давайте я вам заплачу за железку, а вы напроситесь за бесплатно на установку в гимназию?
                                          +1
                                          1) Давно, видимо, вы не общались с нашей системой образования. Если бы всех, кто собирает, сажали бы — у нас не было бы школ. Если бы школы жили на обеспечение из гороно, то похожи были бы на сараи. «Дорогие родители, сегодня скидываемся по 400 грн на телевизор в класс и замену окон» — это вполне нормальная ситуация на каждом родительском собрании. Дилду никто не применяет, к сожалению или к счастью, смотря с какой стороны посмотреть. Если что, инфо, так сказать, от инсайдера, не со стороны.

                                          2) Любая работа должна быть оплачена. Точка. Есть такое свойство у людей, получил бесплатно — сядь на шею. Получил за условные деньги — уважаешь чужой труд. Из личного опыта.

                                          3) Не вижу минуса. Устройство собрано (если под собрано можно назвать пайку простой платы на 5 элементов и подключение десятка проводов), даже если оно лежит на полке, у меня появился отличный опыт, который, как известно, карман не тянет.

                                          Деградация? В штат — звонаря? Я — в луже? Как-то мрачно у вас все =)
                                            0
                                            У меня сыну 9 лет и он так же ходит в одну из Харьковских школ. И про Гороно я пишу потому что регулярно разговариваю и с педколлективом, и с проверяющими, и сам активно участвую в жизни класса и школы.

                                            Я вам предложил вариант. Давайте я оплачу вашу работу и ваще устройство встанет на место. Вы так и не ответили.

                                            Устройство создавалось с определенной целью — контроллировать школьные звонки. Цель не достигнута. Устройство лежит на полке и не работает. С этой позиции — да, вы в луже, т.к. собрать-то собрали, а внедрить — сил не хватило.
                                              +1
                                              Ответил в приват по поводу устройства, если вы не обратили внимания.

                                              Да и да, просто вам критиковать, не зная, кто внедрял, кто разрабатывал. У меня на полке еще десяток различных девайсов и прототипов, которые были сделаны, но не внедрены, видимо я должен был бы в этой луже захлебнуться уже. А как по мне — это нормальный рабочий процесс. Что-то выстреливает, что-то нет. На прототипах обкатываются идеи, проверяются реализации, видно, что улучшить, что поправить. В контексте создается впечатление, что вы думаете про это устройство как про долгосрочный серьезный проект с бюджетом, командой разработки, тестирования и внедрения. Но нет, это проект одного вечера, сделанный, в основном, по приколу и опыта для. То, что оно не применялось, ну и ладно. Миллионером стать я, благодаря этой штуке, не планировал. Продавать ее массово, да еще и за какие-то там серьезные деньги, мне было бы стыдно.

                                              П.с. наверное, у вас какой-то другой Харьков и школы другие. У меня совершенно противоположная картина, навевающая откровенный ужас и отвращение.
                                          0
                                          В расписании звонков время кратно пяти минутам, или бывают минутные значения?
                                            +1
                                            Бывают минутные значения. Можно звонок ставить раз в минуту, если вы про это.
                                            +8
                                            А я подобное делал на ланчпаде.
                                            Правда, без свистелок вроде обновления через сеть. Но зато железяка получилась полностью автономной, и этой самой сети (компьютерной) вообще не требует. Тупо стоит и звонит :).

                                            Про часы реального времени думал, но потом отказался (покуда МК от Ti умеет «очень глубоко спать» — роль RTC он играет сам для себя, будучи вооружённым часовым кварцем).
                                            Изначально планировался двухканальный звонок (чтобы управлять сразу двумя линиями). Но это оказалось невостребованным.
                                            Управление — двумя кнопками (прямо как в старых электронных часах) — одна циклически переходит по пунктам меню, вторая — подтверждает/отменяет действие либо входит в подменю.
                                            Индикатор — какой оказался в наличии (4-строчный текстовый).
                                            Итого из 14 доступных GPIO на борту (MSP430G2452) получилось 2 ноги на кнопки; 6 ног на LCD, 1 нога на звонок, 1 нога на подсветку LCD (изначально предполагалась как 2-й звонок), 1 нога на контроль наличия питания от сети (на ней компаратор), 1 нога на контроль напряжения батареи (на ней АЦП), и последние две ноги — UART tx/rx.
                                            Уроки планируются с точностью до минуты; числа для простоты работы хранятся в BCD (2 байта на урок). + 1 байт битовой маски по дням недели (6 дней + 2 бита выбора канала звонка). Итого в 60 байт умещается полноценное расписание с различными вариациями.
                                            Сами часы можно корректировать (на случай не очень качественного кварца). Но пока ход составил порядка 10 секунд за год работы, поэтому фича так и не востребована.
                                            При пропадании внешнего питания (от розетки) отрубается LCD и блокируется подсветка и звонки. При этом рассчётное время автономной работы (от батареи) такой схемы составляет, оценочно, 3-4 года (UPS для малинки? Фи!). Когда напряжение на батарее падает до 2,8в появится надпись «батарея разряжена».
                                            В случае надобности можно полностью отключить все звонки. Либо дать тестовый (тревожный) звонок из меню с помощью кнопок.
                                            UART на борту предполагался для дистанционной настройки (либо напрямую, либо повесив на него BT) — но за полтора года эксплуатации устанавливать расписание потребовалось ровно два раза (каждый раз это заняло порядка полуминуты), поэтому функция тоже оказалась невостребованной.
                                            Прошивку писал на C++, вооружившись IDE code::blocks и тулчайном msp430gcc. Отлаживал на «старшем брате» MSP430G2553 (там 16К памяти — влезала отладочная версия), однако для релиза хватило младшего MSP430G2452.
                                            Хотел написать статью на хабр, однако удерживает то, что фотографии процесса проектирования/сборки сейчас получить достаточно сложно (девайс давно «в бою», трогать его только рад фотографирования нутра не вижу особого смысла). Хотя если интересно более техническое описание процесса проектирования и решения возникших косяков разводки (ух, на одной плате рядышком МК и тут же силовое электромеханическое реле — та ещё взрывная смесь!), могу описать детальнее.

                                              +1
                                              Конечно пишите! Про MSP-шки на Хабре статей маловато, ваш опыт будет востребован.
                                              0
                                                0
                                                Он умеет расписание? Не вижу в описании. Кажется, это простой вкл-выкл в нужное время 1 раз в сутки. Нет?
                                                  0
                                                  — 10 groups of switch time, manual/automatic operation
                                                  — The time control range from 1 min to 168 hours

                                                  толи 10 раз в сутки, толи вообще 10 раз.
                                                  но факт что можно выставлять день недели и время включения-выключения.
                                                    0
                                                    Я вот тоже не пойму, что такое 10 groups of switch time.

                                                    Тем не менее, слабо применимо. Если в день больше 10 уроков? Если в понедельник расписание не совпадает со вторником? ИМХО девайсина заточена, скорее, на управление по принципу «включить водонагреватель в 2 часа дня, т.к. вечером все моются»
                                                      0
                                                      Я думаю если порыться можно нати на большее колво включений выключений, ну пусть стоит 30 пусть 50… 100$ но готовый девайс и без гемороя
                                                      на крайня поставить 2 таких реле что я кинул, один «первая смена», второй «вторая смена».
                                                      Вопрос в том надо решить задачу дешево, или изобрести велосипед + поднять свой скил :)
                                                        0
                                                        Так продаются готовые системы для школьных звонков. Не дешево. Но тут немного другая идея, и он дааже не совсем о школьных звонках как таковых.
                                                +2
                                                Интересно, сколько же народу столкнулись с необходимостью такого устройства, и сделали их самостоятельно :) Я тоже в свое время развлекался, мой «Звонок» до сих пор работает в старой школе (меня держат в курсе, когда ответственный теряет инструкцию по эксплуатации).

                                                avr-tricks.blogspot.ru/2012/11/blog-post_6.html

                                                Об удалённом управлении… У меня была идея повесить какую-нибудь беспроводную свистелку на UART, но в итоге даже с обыкновенным USB к устройству пришлось подойти раз 5 за всю его историю — несколько раз обновить прошивку и один раз — таблицу звонков.
                                                  0
                                                  Все хорошо до тех пор, пока вы в прямой доступности. А если вы переедите в другой город или даже в другую страну, а им понадобятся новые функции устройства? Тут то и проблема. Даже если оставите исходники, то среднестатистический учитель (а чаще — учительница) информатики, чьи знания находятся на чуть выше книги Windows 95 for Dummies, сделать ничего не смогут. Придется искать специалиста по МК, а их не много. У нас — так очень мало, я бы сказал. С другой стороны, разобраться с сотней строк кода на Пайтоне сможет почти любой айтишник, или, что вероятно, даже ученик этой самой школы.

                                                  устройству пришлось подойти раз 5 за всю его историю — несколько раз обновить прошивку и один раз — таблицу звонков.

                                                  Я выше писал, что расписание сейчас меняется очень часто, и, попервой, лазить туда пришлось бы весьма часто. В будущем, возможно, стабилизировалось бы, но как быстрое решение — предложили вот так.

                                                  Да, можно было сделать на микроконтроллере, со спящим режимом, экраном, все дела. Но заняло бы не менее недели. Может есть профи, способные соорудить подобное за 2-3 дня, но в моем случае от идеи ддо прототипа прошло чуть больше суток. Реализация заняла чуть больше трех часов. Это на девайсину, которая уже желает свою работу.
                                                    0
                                                    Расписание у нас, к счастью, было довольно стабильным. А с простыми сервисными задачами справляется не только наша преподавательница по информатике, но и технички на этаже — там интерфейс в 4 кнопки, всё подробно в инструкции расписано. Просто начинка изначально готовилась по принципу кувалды — пытался сделать так, чтобы ломаться было нечему.

                                                    Если бы у нас на посту охраны стоял компьютер — я бы просто развернул там cron и подключил к LPT релешку ;) Кстати, с Raspberry можно было бы поступить точно так же (естественно, подключив реле к GPIO). Вопрос только идеологический: если бы у меня на тот момент была Raspberry, мне было бы влом её отдавать на скучную и простую работу, я бы ей нашёл задачку повеселее.
                                                  0
                                                  Согласен, что RPi тут излишен. Есть несколько компаний, торгующих более дешёвыми устройствами.

                                                  Однако стоимость разработки устройства на таком чипе превосходит стоимости RPi.

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

                                                  Самое читаемое