Flip-Dot Clock laiko sinchronizavimas su Aviete per Bluetooth

flip-dot clock synch

Persukdamas savo elektromechaninį laikrodį į vasaros laiką, pastebėjau, kad laikrodis skuba daugiau nei viena minute. Kadangi laikrodis surinktas kreivai – šleivai, gali būti, kad RTC veikimą įtakoja kokia nors elektromagnetinė interferencija ar pan. Per daug į tai gilintis patingėjau, nes prisiminiau, kad prie laikrodžio kaip tyčia yra prijungtas HC05 BlueTooth moduliukas, kuris leidžia laikrodžio laiką atnaujinti nuotoliu būdu, o tai yra puiki galimybė laiko neatitikimą nuo realybės išspręsti reguliaria laiko sinchronizacija su RPI, kuri savo ruožtu laiką gauna iš NTP serverio

Pasiruošimas

Įkišam USB Bluetooth adapterį į RPI ir terminale įvykdome šias komandas:

sudo apt-get update
sudo apt-get install bluetooth bluez-utils blueman

Įvykdytos komandos sudiegs visą bendravimui su bluetooth adapteriu reikalingą programinę įrangą. Norint įsitikinti, kad bluetooth modulis susidiegė teisingai, galima įvykdyti šią komandą:

hciconfig

Turėtume pamatyti kažką panašaus į:

hciconfig

Įsitikinti, ar bluetooth veikia galima ir taip:

sudo /etc/init.d/bluetooth status

Beje, tokiu pat būdu, iškilus poreikiui, galima sustabdyti, paleisti ir perkrauti bluetooth komunikatorių. Tam komandoje žodį „status„, atitinkamai, reiktų keisti „start”, „stop”, „restart” ar „force-reload„.

Jeigu bluetooth modulis veikia tvarkingai, galima susirasti aplinkinius įrenginius su komanda:

hcitool scan

hcitool_scan

Įrenginių sąraše turėtų matytis mus dominantis įrenginys. Norint su juo bendrauti, bluetooth įrenginius reikia suporuoti (angl. pair) su komanda:

sudo bluez-simple-agent hci0 xx:xx:xx:xx:xx:xx

Savaime suprantama, vietoje xx:xx:xx:xx:xx:xx reiktų nurodyti savo pasirinkto bluetooth įrenginio MAC adresą. Įvykdžius komandą bei teisingai įvedus įrenginio slaptažodį (PIN kodą), įrenginiai turėtų susiporuoti ir būti pasiruošę tiesioginiam bendravimui. Norint su įrenginiu bendrauti nuosekliuoju protokolu (USART), reikia suderinti RFCOMM protokolo nustatymus:

sudo nano /etc/bluetooth/rfcomm.conf

Faile reikia paredaguoti šias eilutes, įrašant į atitinkamą vietą savo pasirinkto bluetooth įrenginio MAC adresą:

rfcomm1 {
bind yes;
device xx:xx:xx:xx:xx:xx;
channel 1;
comment "Connection to Bluetooth serial module";
}

Išsaugojus failą, įvykdome komandą, kuri sukurs virtualų nuoseklųjį prievadą (angl. serial port) /dev/rfcomm1, per kurį bus galima bendrauti su pasirinktu įrenginiu, mano atveju su flip-dot laikrodžiu:

sudo rfcomm bind all

Pasiruošimas kaip ir baigtas, liko tik susidiegti pySerial biblioteką, kuri leis siųsti ir priimti duomenis nuosekliuoju prievadu.

sudo apt-get install python-serial

Python skriptas

flip-dot_synch

Po ilgų išieškojimų gavosi štai toks nepadoriai ilgas python skriptas (veikimo diagrama viršuje):

import sys
import arrow
import re
import serial
import os
import time
import datetime
from time import sleep
from serial import SerialException 

os.chdir(os.path.abspath('/home/python/'))

f = open('flip-dot_synch.log','a')

sys_date = arrow.now().format('YYYY-MM-DD HH:mm:ss')
s = str(sys_date + ' Flip-Dot Synch: \n')
f.write(s)

#status variables
connected = 0
update = 0

class bcolors: #terminal text color
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'

#config variables
max_seconds_diff = 20

print  bcolors.HEADER + "Flip-Dot Clock auto-config V1.0" + bcolors.ENDC

try:
	bluetoothSerial = serial.Serial( "/dev/rfcomm1", baudrate=9600, timeout=1, stopbits = serial.STOPBITS_ONE, bytesize = serial.EIGHTBITS)
	bluetoothSerial.setRTS(0)
	connected=1
except serial.SerialException:
		print bcolors.FAIL + "No connection to the device could be established" + bcolors.ENDC
		f.write("   No connection to the device could be established! \n")
		f.close()
		exit()
		quit()

if connected:
	bluetoothSerial.flushInput() #flush input buffer, discarding all its contents
	bluetoothSerial.flushOutput()#flush output buffer, aborting current output

	bluetoothSerial.write('x') #quit any existing flip-dot clock config mode
	time.sleep(5)

	eilute = ""
	sys_date = arrow.now().format('YYYY-MM-DD HH:mm:ss')
	flipdot_date = arrow.get('1994-09-01 08:00:00', 'YYYY-MM-DD HH:mm:ss')

	bluetoothSerial.write( 'a' )
	time.sleep(1)

	for x in range (0,10):
		eilute = str(bluetoothSerial.readline())
		mat=re.findall(r"(\d+[-]\d+[-]+\d+\s+\d+[:]\d+[:]+\d*)", str(eilute))
		if mat:
			flipdot_date = arrow.get(str(mat), 'YYYY-MM-DD  HH:mm:ss')

	print " "

	if flipdot_date != arrow.get('1994-09-01 08:00:00', 'YYYY-MM-DD HH:mm:ss'):
		print "Clock  time: %s" %flipdot_date.format('YYYY-MM-DD HH:mm:ss')
		print "System time: %s" %sys_date.format('YYYY-MM-DD HH:mm:ss')

		fd = str(arrow.get(flipdot_date).format('YYYY-MM-DD HH:mm:ss'))
		s = str('   Clock  time: '+ fd +'\n')
		f.write(s)

		sd = str(arrow.get(sys_date).format('YYYY-MM-DD HH:mm:ss'))
		s = str('   System  time: '+  sd +'\n')
		f.write(s)

		time_difference=abs((arrow.get(flipdot_date) - arrow.get(sys_date)).total_seconds())
		print "Difference: %s seconds" %time_difference
		s = str('   Difference: '+ str(time_difference) +'\n')
		f.write(s)

		if time_difference > max_seconds_diff:
			print " "
			print (bcolors.WARNING + "Difference is bigger then %s seconds.." + bcolors.ENDC) %max_seconds_diff
			print "Attempting to update.."
			update = 1 # time needs to be synchronized

		else:
			print " "
			print bcolors.OKGREEN + "Everything seems OK. Bay!"  + bcolors.ENDC
			f.write("   Everything seems OK. Bay!\n")

	else:
		print "Could not read flip-dot clock"
		f.write("   Could not read flip-dot clock \n")
		bluetoothSerial.write( 'x' )
		time.sleep(0.1)
		f.close()
		exit()
		quit()

	# Testavimui
	# update = 1
	# time_difference = 50

	#  Going to time/date update sequence:
	if update == 1:
		# --------------------------- DATE update sequence -------------------------
		if time_difference >= 24*60*60: #date needs to be updated
			print ("%s Date update sequence [" + bcolors.OKBLUE + "start"  + bcolors.ENDC + "]")  %str(arrow.now().format('HH:mm:ss'))	

			bluetoothSerial.write('t')
			time.sleep(1)

			bluetoothSerial.flushInput() #flush input buffer, discarding all its contents
			bluetoothSerial.flushOutput()#flush output buffer, aborting current output

			bluetoothSerial.write(str(arrow.now().format('YY')))
			bluetoothSerial.write('\n')
			time.sleep(0.5)

			bluetoothSerial.write(str(arrow.now().format('MM')))
			bluetoothSerial.write('\n')
			time.sleep(0.5)

			bluetoothSerial.write(str(arrow.now().format('DD')))
			bluetoothSerial.write('\n')
			time.sleep(0.5)

			bluetoothSerial.write( 'y' )
			time.sleep(0.1)			

			for x in range (0,10):
				eilute = str(bluetoothSerial.readline())
				mat=re.findall(r'Success!|Error!?', str(eilute))
				if mat:
					if str(mat)==str("['Success!']"):
						print ("%s Date update sequence [" + bcolors.OKGREEN + "success"  + bcolors.ENDC + "]")	 %str(arrow.now().format('HH:mm:ss'))
						sys_date = arrow.now().format('YYYY-MM-DD HH:mm:ss')
						s = str('     Date update sequence [success] ' + sys_date + '\n')
						f.write(s)
					else:
						print ("%s Date update sequence [" + bcolors.FAIL + "error"  + bcolors.ENDC + "]")  %str(arrow.now().format('HH:mm:ss'))
						sys_date = arrow.now().format('YYYY-MM-DD HH:mm:ss')
						s = str('     Date update sequence [error] ' + sys_date + '\n')
						f.write(s)	

			time.sleep(1)

		else:
			print bcolors.OKGREEN + "Date does not need to be updated!" + bcolors.ENDC
			f.write('     Date does not need to be updated! \n')	

		# --------------------------- TIME update sequence -------------------------
		print ("%s Time update sequence [" + bcolors.OKBLUE + "start"  + bcolors.ENDC + "]")	%str(arrow.now().format('HH:mm:ss'))

		bluetoothSerial.write('t')
		time.sleep(1)

		bluetoothSerial.flushInput() #flush input buffer, discarding all its contents
		bluetoothSerial.flushOutput()#flush output buffer, aborting current output

		bluetoothSerial.write(str(arrow.now().format('HH')))
		bluetoothSerial.write('\n')
		time.sleep(0.5)

		bluetoothSerial.write(str(arrow.now().format('mm')))
		bluetoothSerial.write('\n')
		time.sleep(0.5)

		bluetoothSerial.write(str(arrow.now().format('ss')))
		bluetoothSerial.write('\n')
		time.sleep(0.5)

		bluetoothSerial.write( 'y' )
		time.sleep(0.1)			

		for x in range (0,10):

			eilute = str(bluetoothSerial.readline())	

			# print eilute
			mat=re.findall(r'Success!|Error!?', str(eilute)) #\bSuccess!?\b
			if mat:
				if str(mat)==str("['Success!']"):
					print ("%s Time update sequence [" + bcolors.OKGREEN + "success"  + bcolors.ENDC + "]") %str(arrow.now().format('HH:mm:ss'))
					sys_date = arrow.now().format('YYYY-MM-DD HH:mm:ss')
					s = str('     Time update sequence [success] ' + sys_date + '\n')
					f.write(s)
				else:
					print ("%s Time update sequence [" + bcolors.FAIL + "error"  + bcolors.ENDC + "]")	 %str(arrow.now().format('HH:mm:ss'))
					sys_date = arrow.now().format('YYYY-MM-DD HH:mm:ss')
					s = str('     Time update sequence [error] ' + sys_date + '\n')
					f.write(s)						

		time.sleep(3)
		bluetoothSerial.write( 'x' )
		time.sleep(0.1)
		f.close()
		exit()
		quit()

	else: #noting needs to be updated, can quit now
		f.close()
		exit()
		quit()

else:
	print bcolors.FAIL +  "I said, I was not connected!" + bcolors.ENDC
	f.write('I said, I was not connected! \n')
	f.close()
	exit()
	quit()

bluetoothSerial.write( 'x' )
time.sleep(0.1)
bluetoothSerial.close()
f.write('Normal exit. \n')
f.close()
exit()
quit()

Skirptas gavosi toks ilgas todėl, kad jis laikrodžio laiką ir datą atnaujina ne aklai, tačiau tik tada, kai to reikia, t. y. kai laikrodžio laikas nesutampa su RPI laiku per max_seconds_diff = 20 sekundžių. Tam teko realizuoti aibę visokių papildomų patikrinimų ir apsaugų nuo timeout’ų ir blogo suveikimo. Taip pat skripte realizavau ir logo rašymą į failą flip-dot_synch.log, kuris labai praverčia skriptą leidžiant cron priemonėmis:

30 2,4 * * * sudo python /home/python/flip-dot_synch.py &

Beje, sukurtus logus terminale patogu skaityti su komanda „less”:

less /home/python/flip-dot_synch.log

flipdot_synch_log

Atsisiuntimai

Tie, kas tingi kopijuoti skpripto kodą, gali atsisiųsti viską skriptą iš čia: flip-dot_synch.zip

Naujausią kodo versiją rasite mano GitHub saugykloje!

Naudinga literatūra: