# -*- python -*- # This example adjusts the the colours to make it easier to go to bed # around a scheduled time, for each weekday. # Copyright © 2014 Mattias Andrée (maandree@member.fsf.org) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import time import datetime # Geographical coodinates. # ("Kristall, vertikal accent i glas och stål" (Crystal, vertical accent # in glass and steal) in this example. A glass obelisk, lit from the inside # with adjustable colours and a default colour of 5600 K, in the middle # of a hyperelliptic roundabout.) latitude, longitude = 59.3326, 18.0652 # The time for each weekday you go to bed. The first value is the # time to start preparing the for sleep and the second value is the # time the monitors should be fully adjusted for sleep. time_sleep_monday = ('21:00', '24:00') time_sleep_tuesday = ('21:00', '24:00') time_sleep_wednesday = ('21:00', '24:00') time_sleep_thursday = ('21:00', '24:00') time_sleep_friday = ('21:00', '24:00') time_sleep_saturday = ('23:00', '26:00') time_sleep_sunday = ('23:00', '26:00') # It is allowed to have values above and including 24:00, these # values are interprets as that time (minus 24 hours) the next day. # The time for each weekday you wake up. The first value is the time # to start adjusting the colours back to normal node, and the second # value is the time the adjustment should be back to fully normal. time_wakeup_monday = ('06:00', '07:00') time_wakeup_tuesday = ('06:00', '07:00') time_wakeup_wednesday = ('06:00', '07:00') time_wakeup_thursday = ('06:00', '07:00') time_wakeup_friday = ('06:00', '07:00') time_wakeup_saturday = ('13:00', '14:00') time_wakeup_sunday = ('13:00', '14:00') # The (zero-based) index of the monitors (CRTC:s) to apply # settings to. An empty list means that all monitors are used, # but all monitors will have the same settings. monitors = [] # Gamma correction for the red, green and blue components, # respectively, for each monitor, gamma_red = [1] gamma_green = [1] gamma_blue = [1] # The colour temperature during the day, night, during # sleep and the default, respectively. temperature_day = 5500 temperature_night = 3500 temperature_sleep = 1000 temperature_default = 6500 # The brightness during the day, night, during sleep # and the default respectively. brightness_day = 1 brightness_night = 1 brightness_sleep = 0.2 brightness_default = 1 # Method for applying colour curves. apply_curves = randr #apply_curves = vidmode if ttymode: apply_curves = drm # Method used to get the degree to which it is day. get_dayness = lambda : sun(latitude, longitude) wait_period = 1 ''' :float The number of seconds to wait before invoking `periodically` again ''' fadein_time = 20 ''' :float? The number of seconds used to fade in on start, `None` for no fading ''' fadeout_time = 10 ''' :float? The number of seconds used to fade out on exit, `None` for no fading ''' fadein_steps = 20 * fadein_time if fadein_time is not None else None ''' :int The number of steps in the fade in phase, if any ''' fadeout_steps = 20 * fadeout_time if fadeout_time is not None else None ''' :int The number of steps in the fade out phase, if any ''' # Time constants. ONE_DAY = 24 * 60 * 60 ONE_WEEK = 7 * ONE_DAY # Combine the time points into a matrix. times = (time_sleep_monday + time_wakeup_tuesday, time_sleep_tuesday + time_wakeup_wednesday, time_sleep_wednesday + time_wakeup_thursday, time_sleep_thursday + time_wakeup_friday, time_sleep_friday + time_wakeup_saturday, time_sleep_saturday + time_wakeup_sunday, time_sleep_sunday + time_wakeup_monday) def interpret_time(t): ''' Convert a text representation of a time point to a float point value of the number of seconds @param t:str The time as text @return :float The time as floating point ''' t = [float(t_) for t_ in t.split(':')] while len(t) > 3: t.append(0) return sum([v * 60 ** (2 - i) for i, v in enumerate(t)]) def monotonic_time(ts): ''' Ensure that each time points in a sequence is at least as late as the previous time @param ts:list<float> The time point sequence @return :list<float> The time point sequence as an increasing sequence ''' rc = [ts[0]] for t in ts[1:]: if t < rc[-1]: t += rc[-1] - (rc[-1] % ONE_DAY) if t < rc[-1]: t += ONE_DAY rc.append(t) return rc times = [monotonic_time([interpret_time(t) for t in ts]) for ts in times] # Convert time point matrix to a vector. timepoints = [] for weekday in range(len(times)): weekday_ = weekday * ONE_DAY ts = times[weekday] for ti in range(len(ts)): t = (ts[ti] + weekday_) % ONE_WEEK timepoints.append((t, ti)) timepoints.sort(key = lambda x : x[0]) timepoints.insert(0, (timepoints[-1][0] - ONE_WEEK, timepoints[-1][1])) timepoints.append((timepoints[1][0] + ONE_WEEK, timepoints[1][1])) def get_bedness(time): ''' Calculate to what degree the adjustments should be tuned to bedtime mode @param time:float The number of seconds in the time modulo the a week @return :float To what degree the adjustments should be tuned to bedtime mode ''' for i in range(len(timepoints) - 1): if timepoints[i][0] <= time <= timepoints[i + 1][0]: break (a, p), (b, _) = timepoints[i], timepoints[i + 1] weight = (time - a) / (b - a) if p == 0: return weight if p == 1: return 1 if p == 2: return 1 - weight return 0 last_dayness, last_bedness = -1, -1 def periodically(year, month, day, hour, minute, second, weekday, fade): ''' Invoked periodically If you want to control at what to invoke this function next time you can set the value of the global variable `wait_period` to the number of seconds to wait before invoking this function again. The value does not need to be an integer. @param year:int The year @param month:int The month, 1 = January, 12 = December @param day:int The day, minimum value is 1, probable maximum value is 31 (*) @param hour:int The hour, minimum value is 0, maximum value is 23 @param minute:int The minute, minimum value is 0, maximum value is 59 @param second:int The second, minimum value is 0, probable maximum value is 60 (**) @param weekday:int The weekday, 1 = Monday, 7 = Sunday @param fade:float? Blueshift can use this function to fade into a state when it start or exits. `fade` can either be negative, zero or positive or `None`, but the magnitude of value cannot exceed 1. When Blueshift starts, this function will be invoked multiple with the time parameters of the time it is invoked and each time `fade` will increase towards 1, starting at 0, when the value is 1, the settings should be applied to 100 %. After this this function will be invoked once again with `fade` being `None`. When Blueshift exits the same behaviour is used except, `fade` decrease towards -1 but start slightly below 0, when -1 is reached all settings should be normal. Then Blueshift will NOT invoke this function with `fade` being `None`, instead it will by itself revert all settings and quit. (*) Can be exceeded if the calendar system is changed, like in 1712-(02)Feb-30 (**) See https://en.wikipedia.org/wiki/Leap_second ''' global last_dayness, last_bedness tzoff = (datetime.datetime.now().hour - datetime.datetime.utcnow().hour) * 60 * 60 tzoff += (datetime.datetime.now().minute - datetime.datetime.utcnow().minute) * 60 now = time.time() + tzoff h = int((now / (60 * 60)) % 24) if h < hour: weekday += 1 dayness = get_dayness() bedness = get_bedness((weekday - 1) * ONE_DAY + (now % ONE_DAY)) # Do not apply new adjustments if nothing has changed. if (fade is None) and (dayness == last_dayness) and (bedness == last_bedness): return last_dayness, last_bedness = dayness, bedness # Calculate temperature and brightness. temperature_ = temperature_day * dayness + temperature_night * (1 - dayness) brightness_ = brightness_day * dayness + brightness_night * (1 - dayness) temperature_ = temperature_sleep * bedness + temperature_ * (1 - bedness) brightness_ = brightness_sleep * bedness + brightness_ * (1 - bedness) if fade is not None: alpha = abs(fade) temperature_ = temperature_ * alpha + temperature_default * (1 - alpha) brightness_ = brightness_ * alpha + brightness_default * (1 - alpha) # Remove settings from last run. start_over() # Apply colour temperature using raw CIE 1964 10 degree CMF data with interpolation. temperature(temperature_, lambda t : clip_whitepoint(divide_by_maximum(cmf_10deg(t)))) # Apply colour brightness using the CIE xyY colour space. cie_brightness(brightness_) # Store calculates so that they can be reused for each monitor stored = store() for m in range(max(1, len(monitors))): gamma_red_ = gamma_red [m % len(gamma_red)] gamma_green_ = gamma_green[m % len(gamma_green)] gamma_blue_ = gamma_blue [m % len(gamma_blue)] # Reuse stored calculations. restore(stored) # Apply gamma correction to monitor. gamma(gamma_red_, gamma_green_, gamma_blue_) # Flush settings to monitor. if len(monitors) == 0: apply_curves() else: apply_curves(monitors[m % len(monitors)]) def reset(): ''' Invoked to reset the displays ''' for m in range(max(1, len(monitors))): gamma_red_ = gamma_red [m % len(gamma_red)] gamma_green_ = gamma_green[m % len(gamma_green)] gamma_blue_ = gamma_blue [m % len(gamma_blue)] # Remove settings from last run. start_over() # Apply gamma correction to monitor. gamma(gamma_red_, gamma_green_, gamma_blue_) # Flush settings to monitor. if len(monitors) == 0: apply_curves() else: apply_curves(monitors[m % len(monitors)])