#!/usr/bin/env python ''' This small program is expected to be run twice. The procedure is: 1. Run this program with the -b (begin measurement) option. 2. Maybe a week later, run it again with the -e (end measurement) option. The -b and -e arguments can optionally be augmented with a -d date argument. If you don't use the -d arg, you will be prompted to enter the calculator date. If you use -d, it must be followed by exactly 6 integers, which are: year month day hour minute second. To this point, here is what happened: the -b option uses the clock on your PC, hopefully NTP controlled, and creates a file named calTimes in the directry where you run rtcal.py. The file might look like this: calc: 2024 8 15 13 13 25 0 ntp: 2024 8 15 13 13 25 663658 where the first line is the calculator clock time that you manually entered. The second line is base on your computer's clock. After a week, run the program again but with the -e argument and be sure to run from the directory containing the calTimes file. Using elapsed time between NTP clock values and elapsed time between calculator clock values, the time lost or gained by the calculator compared to NTP is determined. That value is used in the small formulas given in the SwissMicors manual and file named rtccalib.cfg is created containing a single number, the correction value. Move the file to the root directory of your calculator. A typical run sequence might be to set the calculator clock and then: rtcal.py -b -d 2024 8 15 13 25 0 # Begin measurement with calculator's date. # ...A blissful several days pass... rtcal.py -e -d 2024 8 23 8 44 30 # End measurement with calculator's date. # rtccalib.cfg has been created. cp rtccalib.cfg /media/mm/DM42 # or where ever your calc is mounted. # Set your calculator clock to proper time. Mike Markowski, mike.ab3ap@gmail.com Aug 2024 ''' import datetime, os, sys # Minimize delay and grab computer time immediately. timeNtp = datetime.datetime.today() # Date & time right now. def enterCalcDate(): '''If calculator date and time are not provided, prompt user for them. Return calculator and NTP times as datetime items. ''' print('Enter calculator date as integers:') Y = int(input(' Year: ')) M = int(input(' Month: ')) D = int(input(' Day: ')) h = int(input(' Hour: ')) m = int(input(' Min: ')) s = int(input(' Sec: ')) n = datetime.datetime.today() # NTP date & time right now. c = datetime.datetime(Y,M,D,h,m,s,0) # Calc date & time as entered. return c,n def getStartTimes(filename): '''At end of measurement, open previously saved file containing calculator and NTP times at the moment the measurement began. Return both measurement start times as datetime items. ''' dCalc0 = dNtp0 = None with open(filename, 'r') as f: # Open previously saved start times file. for line in f: line = line.split() if line[0] not in['calc:', 'ntp:']: print('%s: unexpected line:\n%s' % (filename, line)) sys.exit(1) Y = int(line[1]) M = int(line[2]) D = int(line[3]) h = int(line[4]) m = int(line[5]) s = int(line[6]) us = int(line[7]) if line[0] == 'calc:': dCalc0 = datetime.datetime(Y,M,D,h,m,s,us) else: # 'ntp:' dNtp0 = datetime.datetime(Y,M,D,h,m,s,us) if dCalc0 == None: print('%s: calculator time not found in start time file.') sys.exit(1) if dNtp0 == None: print('%s: NTP time not found in start time file.') sys.exit(1) return dCalc0, dNtp0 def meastBegin(filename, c=None, n=None): '''After a start or stop calculator has been entered, it and the corresponding NTP time are stored in a file for use some days later. ''' if c == None: c,n = enterCalcDate() timeCalc = [c.year, c.month, c.day, c.hour, c.minute, c.second, c.microsecond] timeNtp = [n.year, n.month, n.day, n.hour, n.minute, n.second, n.microsecond] Y,M,D,h,m,s,us = timeCalc f = open(filename, 'w') print('calc: %d %d %d %d %d %d %d' % (Y,M,D,h,m,s,us), file=f) Y,M,D,h,m,s,us = timeNtp print('ntp: %d %d %d %d %d %d %d' % (Y,M,D,h,m,s,us), file=f) f.close() def meastEnd(filename, prog, dCalc1=None, dNtp1=None): '''Once we have measurement start & stop times for both calculator and NTP, this subroutine is the meat of the program. It calculates the correction factor to be put on the calculator. There is no subroutine return value, but the calculated result is stored in newly created file, rtccalib.cfg, the name expected by the calculator. ''' dCalc0,dNtp0 = getStartTimes(filename) # Get saved start times. if dCalc1 == None: dCalc1,dNtp1 = enterCalcDate() # Get current times. spanCalc_s = (dCalc1 - dCalc0).total_seconds() # Elapsed calculator time. spanNtp_s = (dNtp1 - dNtp0).total_seconds() # Elapsed NTP time. # spanNtp_s = 24*7*60*60 # Force to exactly 1 week to test. ahead_s = spanCalc_s - spanNtp_s # Seconds calculator is ahead of NTP. P = -ahead_s/spanNtp_s*1e6 # PPM. C = 2**20 * P/(1e6 + P) # Correction. if not (-511 <= C <= 512): print('C=%.2f' % C) print('Clock is too far out of sync. Be sure you entered dates') print('correctly. If necessary, re-measure shorter time period.') sys.exit(1) f = open('rtccalib.cfg', 'w') print('%f' % C, file=f) # print('%d' % round(C), file=f) f.close() print('Move rtccalib.cfg to calculator root dir, then set time.') def usage(prog): p = os.path.basename(prog) print('Usage: %s -b|-e [-d Y M D h m s] [-f filename]' % prog) print(' -b begin measurement') print(' -e end measurement') print(' -d calculator date, yr mo day hr min sec, all integers') print(' -f filename to store times, default is "calTimes"\n') print(' Run "%s -e" several days after "%s -b".' % (prog,prog)) def main(argv, timeNtp): '''The program! A typical run might be: rtcal.py -b -d 2024 8 15 13 25 0 # Begin measurement. rtcal.py -e -d 2024 8 23 8 44 30 # End measurement, days later. cp rtccalib.cfg /media/mm/DM42 # Copy clock cal file to calc! ''' prog = os.path.basename(argv[0]) argv = argv[1:] if len(argv) == 0: usage(prog) sys.exit(1) filename = 'calTimes' timeCalc = None # Calculator start or stop date. i = 0 while i < len(argv): arg = argv[i] if arg == '-b': fn = 'begin' elif arg == '-d': # Followed by 6 date integers. try: i += 1; Y = int(argv[i]) i += 1; M = int(argv[i]) i += 1; D = int(argv[i]) i += 1; h = int(argv[i]) i += 1; m = int(argv[i]) i += 1; s = int(argv[i]) timeCalc = datetime.datetime(Y,M,D,h,m,s) except IndexError: print('Ignoring bad date.') except ValueError: print('Ignoring bad date.') elif arg == '-e': fn = 'end' elif arg == '-f': i += 1 filename = argv[i] else: usage(prog) sys.exit(1) i += 1 if fn == 'begin': meastBegin(filename, timeCalc, timeNtp) else: # 'end' meastEnd(filename, prog, timeCalc, timeNtp) if __name__ == '__main__': main(sys.argv, timeNtp)