#!/usr/bin/env python3 # See LICENSE file for copyright and license details. import sys, os, pwd, time import solar_python as sol loc = None stop_date = None human = False unix = False local = False noon = False night = False blue = False gold = False unblue = False ungold = False morning = [] evening = [] dderiv = [] nderiv = [] res = '1s' ## Parse command line argv0 = sys.argv[0] if len(sys.argv) > 0 else 'blue' def usage(): opts = '[-d delev]* [-D delev]* [-e elev]* [-m elev]* [-h [-L] | -u | -L]' opts += ' [-l lat:lon | -l loc] [-s year-month-day | -s -] [-r num[h|m|s]]' opts += ' [-bBgGnN]' print('Usage: %s %s' % (argv0, opts), file = sys.stderr) sys.exit(1) i, n = 1, len(sys.argv) def getarg(): global i, j, n, m if j < m: rc = arg[j:] j = m return rc else: j = m i += 1 if i == n: usage() return sys.argv[i] while i < n: arg = sys.argv[i] if arg == '--': i += 1 break elif arg.startswith('-'): j, m = 1, len(arg) while j < m: c = arg[j] j += 1 if c == 'd': dderiv.append(getarg()) elif c == 'D': nderiv.append(getarg()) elif c == 'e': evening.append(getarg()) elif c == 'm': morning.append(getarg()) elif c == 'l': loc = getarg() elif c == 's': stop_date = getarg() elif c == 'r': res = getarg() elif c == 'b': blue = True elif c == 'B': unblue = True elif c == 'g': gold = True elif c == 'G': ungold = True elif c == 'h': human = True elif c == 'u': unix = True elif c == 'L': local = True elif c == 'n': noon = True elif c == 'N': night = True else: usage() i += 1 else: break if i != n or (human and unix): usage() ## Parse resolution if len(res) < 2: usage() mul = {'s' : 1.0, 'm' : 60.0, 'h' : 60 * 60.0} suffix = res[-1] value = res[:-1] if '.' in value: usage() try: res = int(value) * mul[suffix] except: usage() ## Parse elevations try: evening = [float(e) for e in evening] morning = [float(e) for e in morning] dderiv = [float(e) for e in dderiv] nderiv = [float(e) for e in nderiv] for e in evening + morning: if abs(e) > 90: usage() except: usage() ## Parse -bBgGnN if blue: morning.append(sol.SOLAR_ELEVATION_RANGE_BLUE_HOUR[0]) evening.append(sol.SOLAR_ELEVATION_RANGE_BLUE_HOUR[1]) if unblue: morning.append(sol.SOLAR_ELEVATION_RANGE_BLUE_HOUR[1]) evening.append(sol.SOLAR_ELEVATION_RANGE_BLUE_HOUR[0]) if gold: morning.append(sol.SOLAR_ELEVATION_RANGE_GOLDEN_HOUR[0]) evening.append(sol.SOLAR_ELEVATION_RANGE_GOLDEN_HOUR[1]) if ungold: morning.append(sol.SOLAR_ELEVATION_RANGE_GOLDEN_HOUR[1]) evening.append(sol.SOLAR_ELEVATION_RANGE_GOLDEN_HOUR[0]) if noon: dderiv.append(0.0) if night: nderiv.append(0.0) ## Fallback options if len(morning) == 0 and len(evening) == 0 and len(dderiv) == 0 and len(nderiv) == 0: morning.append(sol.SOLAR_ELEVATION_RANGE_BLUE_HOUR[0]) evening.append(sol.SOLAR_ELEVATION_RANGE_BLUE_HOUR[1]) ## Get geolocation def get_geolocation_from_conf(filename, ispath = False): filenames, lat, lon = [], None, None if ispath: filenames.append(filename) else: if 'HOME' in os.environ and len(os.environ['HOME']) > 0: filenames.append(os.environ['HOME'] + '/.config/' + filename) try: filenames.append(pwd.getpwuid(os.getuid()).pw_dir + '/.config/' + filename) except: pass filenames.append('/etc/' + filename) for filename in filenames: try: with open(filename, 'rb') as file: loc = file.read().decode('utf-8', 'replace').split('\n')[0] (lat, lon) = loc.split(' ') lat, lon = float(lat), float(lon) break except: lat, lon = None, None return (lat, lon) if loc is not None: try: (lat, lon) = loc.split(':') lat = float(lat) lon = float(lon) if (abs(lat) > 90 or abs(lon) > 180): lat = None except: lat = None if lat is None: if loc.startswith('./') or loc.startswith('../') or loc.startswith('/'): locfile = loc else: locfile = 'geolocation.d/' + loc (lat, lon) = get_geolocation_from_conf(locfile, locfile is loc) if lat is None: print('%s: bad location: %s' % (argv0, loc), file = sys.stderr) sys.exit(1) else: (lat, lon) = get_geolocation_from_conf('geolocation') if lat is None: print('%s: no geolocation set' % argv0, file = sys.stderr) sys.exit(1) ## Parse stop date if stop_date is not None: if stop_date == '-': stop_date = ... else: try: tm = time.strptime(stop_date, '%Y-%m-%d') stop_date = (tm.tm_year, tm.tm_mon, tm.tm_mday) except: usage() else: tm = time.localtime() if tm.tm_mon < 12: y, m, d = tm.tm_year, tm.tm_mon + 1, tm.tm_mday - 1 else: y, m, d = tm.tm_year + 1, 1, tm.tm_mday - 1 if d < 1: m -= 1 if m < 1: m = 12 y -= 1 days = 29 if y % 400 == 0 or (y % 4 == 0 and not y % 100 == 0) else 28 days = [31, days, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] d = days[m - 1] stop_date = (y, m, d) ## Get stop time if stop_date is ...: stop_time = None else: stop_time = '%i-%02i-%02i 12:00:00' % stop_date guessed = time.mktime(time.strptime(stop_time, '%Y-%m-%d %H:%M:%S')) is_summer = False for tzname, summer in zip(time.tzname, (False, True)): if time.mktime(time.strptime('%s %s' % (stop_time, tzname), '%Y-%m-%d %H:%M:%S %Z')) == guessed: is_summer = summer break (y, m, d) = stop_date days = 29 if y % 400 == 0 or (y % 4 == 0 and not y % 100 == 0) else 28 days = [31, days, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] stop_time = d - 1 for i in range(m - 1): stop_time += days[i] y -= 1 stop_time += y * 365 + y // 4 - y // 100 + y // 400 stop_time -= 1969 * 365 + 1969 // 4 - 1969 // 100 + 1969 // 400 stop_time *= 24 * 60 * 60 stop_time += 24 * 60 * 60 stop_time -= (time.altzone if is_summer else time.timezone) print('Z ', time.gmtime(stop_time)) def get_elev(elev, start, morning): end = start + 0.0099 while start < end: rc = sol.future_elevation(lat, lon, elev, start) if rc is None: return (None, end) else: start = rc + 1e-06 d = sol.solar_elevation(lat, lon, rc + 3e-09) d -= sol.solar_elevation(lat, lon, rc) if d >= 0 if morning else d <= 0: return (sol.julian_centuries_to_epoch(rc), start) return (None, end) def get_deriv(delev, start, daytime): end = start + 0.0099 while start < end: rc = sol.future_elevation_derivative(lat, lon, delev, start) if rc is None: return (None, end) else: start = rc + 1e-06 elev = sol.solar_elevation(lat, lon, rc) if elev >= 0 if daytime else elev <= 0: return (sol.julian_centuries_to_epoch(rc), start) return (None, end) def get_morning(elev, start): return get_elev(elev, start, True) def get_evening(elev, start): return get_elev(elev, start, False) def get_day(delev, start): return get_deriv(delev, start, True) def get_night(delev, start): return get_deriv(delev, start, False) now = sol.epoch_to_julian_centuries(time.time()) class State: def __init__(self, val, fun): self.start = now self.val = val self.fun = fun def next(self): (rc, self.start) = self.fun(self.val, self.start) return (rc, self.start) morning = [State(v, get_morning) for v in morning] evening = [State(v, get_evening) for v in evening] dderiv = [State(v, get_day) for v in dderiv] nderiv = [State(v, get_night) for v in nderiv] states = morning + evening + dderiv + nderiv # So we can stop of the pipe breaks (useful with -s -) def print(msg): msg = (str(msg) + '\n').encode('utf-8') ptr, n = 0, len(msg) while ptr < n: ptr += os.write(1, msg[ptr:]) def parse_time(t, localtime, tzname): tm = time.localtime(t) if localtime else time.gmtime(t) if localtime and tm.tm_isdst < 0: tm = time.gmtime(t) localtime = False if not localtime: tz = 'UTC' if tzname else 'Z' else: tz = time.strftime('%Z' if tzname else '%z', tm) return (tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tz) jc_stop_time = sol.epoch_to_julian_centuries(stop_time) if stop_time is not None else None months = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec') try: while len(states) > 0: n = len(states) i = 0 while i < n: (t, s) = states[i].next() if t is not None and (stop_time is None or t < stop_time): t = int(t / res + 0.5) * res if unix: print(int(t)) else: t = parse_time(t, local, human) if human: (y, m, d, H, M, S, z) = t t = (y, m, months[m - 1], d, H, M, S, z) print('%i-(%02i)%s-%02i %02i:%02i:%02i %s' % t) else: print('%i-%02i-%02iT%02i:%02i:%02i%s' % t) if jc_stop_time is not None and s >= jc_stop_time: del states[i] n -= 1 else: i += 1 except KeyboardInterrupt as e: sys.exit(0) except BrokenPipeError as e: sys.exit(0)