📜 ⬆️ ⬇️

School bell on Raspberry Pi with remote control

Good day, dear habrovchane. It is no secret that single-board Linux computers based on SoC today are widespread among both amateurs and more or less professional users. More and more tasks can be solved with the help of microcomputers, and even those tasks that were previously solved solely with the help of microcontrollers. It would seem that the use of a full-fledged, albeit small, computer for solving simple tasks is one more overkill, but let's see, is this so bad? This article is the answer to our small dispute with devzona habrovchanin about this.

Prehistory


It would seem that there could be a more obvious niche for the use of microcontrollers than the automation of the school bell? This is exactly what an unknown developer thought about 5-7 years ago when he assembled such a wonderful device.

Apparently, collected on the MK Series 8050, has on board a real-time watch, can show this time on a self-made LED matrix, and most importantly, it can pull the rail from time to time, including the school bell. The device has been working successfully for many years, there were no complaints about it. However, everything is flowing and changing, and once a simple Kharkov school with an in-depth study of something there decided to undergo re-certification in the lyceum with an even more in-depth study of the very same. Such re-certification, among other things, requires a transition from 45-minute lessons to couples, consisting of two academic hours of 40 minutes. This is where the trouble came. The developer of the watch on the MK safely drank himself went abroad, did not leave the source code, did not provide for the possibility of reconfiguration. It was with this problem that my friend Kostya knocked me on Skype one autumn day.

After examining the patient, it became clear that in less than a couple of weeks he would not be able to alter it to the requirements of the customer. In fact, you need to rewrite the code from scratch. And, suddenly, in the evening of the same day, a courier from DHL brought me another Raspberry. Here the idea came to make your watch, and not just watch, but with magic. After all, we have a whole microcomputer with full-fledged Linux not onboard, our hands are untied, the possibilities are endless!

Formulation of the problem


In the morning, after negotiations with the customer, the task was set as follows: the device must be configured using any PC, without additional software (expensive), be able to pull up the exact time from the Internet (by calling you can synchronize the clock, all calls are strictly accurate to the second), be able to work autonomously, and, as an additional option for the future, should be able to receive call configuration from a remote server. For example, a district can independently lay out a configuration of calls for educational institutions of a certain type. Task set, proceed to implementation.
')
To implement the project we need the following:



I deliberately miss the initial configuration of the Raspberry Pi, the Internet is full of materials on installing the distribution, setting up the network, time zone, etc.

So let's get started.

Real time clock


As a real-time clock for the device, I took a small scarf on the DS1302, simply because it was found in my handful of trash ordered from China. A wonderful article appeared on the net describing the connection of this particular watch to the raspberry. Connection is pretty simple.



On the same page is available for download software that can receive and record data in these RTC. I rewrote the software a bit for myself in order to visualize the RTC readings before synchronizing with the system time.

Properly, the watch should be updated if the Malinki time is successfully synchronized with the NTP server, and if there is no access to the NTP server, then the Malinki system clock should be synchronized with the real time clock. This algorithm is necessary, since the DS1302 has a habit of crawling away for a couple of seconds a day, which is unpleasant. However, I did not find how to make ntpd run the script after successful synchronization. Therefore, such a crutch was born:

/ 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 


... and activate autoload:
 sudo update-rc.d rtc defaults 


These two files allow you to synchronize Malinka's system clock with the RTC in the event that no network is detected after the download, or to update the time in the RTC if the network is found. After 30 seconds after loading ntpd should have time to update the system clock. In the worst case, the RTC will be recorded recently when Raspberry was turned on. I know that this solution is far from ideal, but I could not think of a better one. The only thing that comes to mind is to add a line to kroons to update the RTC once every 2-3 hours in order to be sure that there are more or less accurate data in the real-time clock. If a highly respected community prompts the best solution, I will be only too happy.

Web server


It did not take long to think. The main task of the server is to show two pages and process one POST request. The textbook implementation of the Python web server simply suggests itself.

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() 


Out of boredom and for better extensibility, even a simple template engine was added. Please note that the interpreter is registered at the beginning of the script, so after installing the permissions for execution, the script can be run directly from the command line.

What makes this script, I think, is clear and without comment. The GET request handler simply gives the client two forms and the main page, filling the variable with data about the current schedule. The POST request handler saves the data from the form to a JSON file, which is the base of calls.

Actually, the manager of the school bell


Thanks to the wonderful GPIO library for Python, blinking a school bell with a raspberry is very simple. This script deals with:

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() 


The script creates a new thread, which checks the time every second. If the time is found in the schedule file, then for 5 seconds we include the call (we submit a high level to the 25 GPIO pin). After each call, we reread the schedule, in case it was changed from the web interface. Everything is transparent and simple.

Demonizing and training the observation dog


Acting by analogy with the autorun RTC synchronization, we create the following files:

/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 



And scripts "watchdogs" for them. These scripts check if the service is running and, if necessary, run it.

/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 


Similarly, we make these scripts automatically loadable at system startup:

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

And we add to the crown new tasks:

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


Now we can be sure that both daemons have started and will work stably. Do not forget to add a new line at the end of wd.cron, otherwise crond will ignore it!

A little about power electronics


The entire power unit is assembled completely standard. The total power of calls in the school is about 0.5 kW, so that the BC137X triac paired with the MOC3061 optocoupler is quite enough to switch this farm. As practice has shown, 3.3 volts of a logical unit is sufficient for the confident inclusion of an optocoupler.


It would be possible to apply here and relays, but somehow I do not trust contacts when there are such wonderful semiconductors. I deliberately do not post a photograph of the layout, because before the beautiful installation never came.

What is missing


Of course, having a full-fledged Linux-computer at your disposal, you can "turn on" the functionality to infinity, and the development time will be relatively short. This circumstance speaks in favor of the use of microcomputers for solving problems that the microcontroller would seem to cope with. However, all the same I will list what, in my opinion, the current implementation lacks:

First , safety. It would be worth confusing at least HTTP-Auth at least, or, having added a little script, make a password database for entering the system’s “admin panel”. And it’s worthwhile to work on data filtering both before and after submitting the form.
Secondly , add / remove academy should be added. hours in shape. The attentive reader noted that this can be done simply by adding the necessary fields to the form on the client side using, for example, simple JavaScript code.
Thirdly , I so wanted to make a “panic button” on the main one, which would launch a call in 5-10 seconds. Let it be a small task for the inquisitive minds of readers, good, everything necessary for this is in the article.
Fourth , there is a shortage of uninterruptible power supply. Due to the failure of the customer to develop, we have not reached it.

How it all ended


Unfortunately, the Kharkov gymnasium with in-depth study of something there decided that it was very, very difficult to collect 3 hryvnias from each parent, and we were eventually turned around, so the implementation stopped on the current prototype, which does not contain some important for finite element system. But the time spent on development was not in vain. I hope that experience in developing applications for working with iron in Python will come in handy more than once in life, especially in the suburban area where the construction of a house ends, which provides for the possibility of managing everything from a single think tank. If I could manage the call, then I can turn on the lights on a schedule.

Afterword


I hope, dear readers, I managed to convey to you the main idea. The use of microcomputers for seemingly trivial tasks can raise their implementation to a fundamentally new level, and instead of the simplest implementations, proprietary protocols and complex support, we get a flexible system with almost infinite extensibility, which in future will result not only in usability, but also in substantial cost savings.

A little more than three hours were spent on the implementation of all of the above. Bringing to mind that there is another need. Traditionally, I will ask you not to kick the code and possible errors for the crooked places. This is my first article on Habré, and the first implemented project in Python. Always happy to amend, suggestions and suggestions. Screenshots and video work lay on demand.

I look forward to the implementation of similar functionality by Komrad devzona , but only on the basis of Arduino. I'm sure I have something to learn from him in terms of developing devices on microcontrollers. The article promises to be truly exciting.

Source: https://habr.com/ru/post/207138/


All Articles