Optimize sunset.py
This commit is contained in:
parent
310b879416
commit
c1c8a83ec5
5 changed files with 103 additions and 95 deletions
|
@ -122,7 +122,7 @@
|
||||||
(defwidget wallpaper []
|
(defwidget wallpaper []
|
||||||
(clicker :text "" :command "~/scripts/swww_change.py"))
|
(clicker :text "" :command "~/scripts/swww_change.py"))
|
||||||
(defwidget sunset []
|
(defwidget sunset []
|
||||||
(clicker :text "" :command "~/scripts/sunset.py"))
|
(clicker :text "" :command "python -OO ~/scripts/sunset.py"))
|
||||||
|
|
||||||
|
|
||||||
(defpoll brightness :interval 60 "~/.config/eww/scripts/backlight get")
|
(defpoll brightness :interval 60 "~/.config/eww/scripts/backlight get")
|
||||||
|
|
57
scripts/lib/evening.py
Normal file
57
scripts/lib/evening.py
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
type Range = tuple[float, float]
|
||||||
|
|
||||||
|
|
||||||
|
def lerped_amount(x: float, edges: Range) -> float:
|
||||||
|
"""How far `x` is between `values`"""
|
||||||
|
return (x - edges[0]) / (edges[1] - edges[0])
|
||||||
|
|
||||||
|
|
||||||
|
def smoothstep(x: float, edges: Range) -> float:
|
||||||
|
"""Smoothly interpolates between the `edges` based on `x`"""
|
||||||
|
# Technically I should bounds check but whatever
|
||||||
|
return (x * x * (3.0 - 2.0 * x)) * (edges[1] - edges[0]) + edges[0]
|
||||||
|
|
||||||
|
|
||||||
|
def day_elapsed(hours: int = 0, minutes: int = 0, seconds: int = 0) -> float:
|
||||||
|
"""Converts (H, M, S) into [0, 1] representing how far through the day it is"""
|
||||||
|
return ((((hours * 60) + minutes) * 60) + seconds) / (20 * 60 * 60)
|
||||||
|
|
||||||
|
|
||||||
|
def now_elapsed() -> float:
|
||||||
|
"""How far through the day it is"""
|
||||||
|
now = datetime.now()
|
||||||
|
return day_elapsed(now.hour, now.minute, now.second)
|
||||||
|
|
||||||
|
|
||||||
|
SUNRISE_HOURS = (5, 7)
|
||||||
|
SUNSET_HOURS = (21, 23)
|
||||||
|
SUNRISE_ELAPSED = (day_elapsed(SUNRISE_HOURS[0]), day_elapsed(SUNRISE_HOURS[1]))
|
||||||
|
SUNSET_ELAPSED = (day_elapsed(SUNSET_HOURS[0]), day_elapsed(SUNSET_HOURS[1]))
|
||||||
|
|
||||||
|
|
||||||
|
def sunup_amount() -> float:
|
||||||
|
"""What temperature the monitor should be"""
|
||||||
|
elapsed = now_elapsed()
|
||||||
|
|
||||||
|
if elapsed <= SUNRISE_ELAPSED[0]:
|
||||||
|
return 0
|
||||||
|
elif elapsed < SUNRISE_ELAPSED[1]:
|
||||||
|
return lerped_amount(elapsed, SUNRISE_ELAPSED)
|
||||||
|
elif elapsed <= SUNSET_ELAPSED[0]:
|
||||||
|
return 1
|
||||||
|
elif elapsed < SUNSET_ELAPSED[1]:
|
||||||
|
return 1 - lerped_amount(elapsed, SUNSET_ELAPSED)
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def on_steam() -> bool:
|
||||||
|
"""Whether the Steam PID is in use"""
|
||||||
|
pid_file = Path("~/.steampid").expanduser().resolve(strict=True)
|
||||||
|
pid = pid_file.read_text().strip()
|
||||||
|
pid_dir = Path("/proc") / pid
|
||||||
|
return pid_dir.is_dir()
|
37
scripts/lib/sunset.py
Normal file
37
scripts/lib/sunset.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
"""Sets the monitor temperature based on the current time using Hyprsunset"""
|
||||||
|
from os import environ
|
||||||
|
from socket import AF_UNIX, SOCK_STREAM, socket
|
||||||
|
|
||||||
|
from .evening import on_steam, Range, smoothstep, sunup_amount
|
||||||
|
|
||||||
|
|
||||||
|
def setup_socket() -> socket:
|
||||||
|
"""Connects to the Hyprsunset socket"""
|
||||||
|
hyprsunset = socket(AF_UNIX, SOCK_STREAM)
|
||||||
|
hyprsunset.connect(f"{environ["XDG_RUNTIME_DIR"]}/hypr/{environ["HYPRLAND_INSTANCE_SIGNATURE"]}/.hyprsunset.sock")
|
||||||
|
return hyprsunset
|
||||||
|
|
||||||
|
|
||||||
|
def get_temperature(hyprsunset: socket) -> int:
|
||||||
|
"""Retrieves the current screen temperature"""
|
||||||
|
# In theory the message might not send in one go, but in practice it does so I won't bother handling the error
|
||||||
|
_ = hyprsunset.send(b"temperature")
|
||||||
|
# 4 bytes should be enough but why not 8 for comfort
|
||||||
|
# Just raise an error if it's not a number, nothing special to do here
|
||||||
|
return int(hyprsunset.recv(8))
|
||||||
|
|
||||||
|
|
||||||
|
def set_temperature(hyprsunset: socket, temperature: float) -> None:
|
||||||
|
"""Sends a message to hyprsunset to set the temperature"""
|
||||||
|
# In theory the message might not send in one go, but in practice it does so I won't bother handling the error
|
||||||
|
_ = hyprsunset.send(f"temperature {temperature:.1f}".encode())
|
||||||
|
|
||||||
|
|
||||||
|
def main(temperature_range: Range, /, check_steam: bool = False) -> None:
|
||||||
|
"""Adjusts the monitor temperature based on the current time"""
|
||||||
|
hyprsunset = setup_socket()
|
||||||
|
temperature = int(smoothstep(sunup_amount(), temperature_range))
|
||||||
|
if temperature != get_temperature(hyprsunset):
|
||||||
|
if check_steam and on_steam():
|
||||||
|
return
|
||||||
|
set_temperature(hyprsunset, temperature)
|
|
@ -1,109 +1,23 @@
|
||||||
#!/bin/env python
|
#!/bin/env python
|
||||||
"""Sets the monitor temperature based on the current time using Hyprsunset"""
|
|
||||||
from argparse import ArgumentParser, Namespace
|
from argparse import ArgumentParser, Namespace
|
||||||
from datetime import datetime
|
|
||||||
from os import environ
|
|
||||||
from pathlib import Path
|
|
||||||
from socket import AF_UNIX, SOCK_STREAM, socket
|
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
|
from lib.sunset import main
|
||||||
type Range = tuple[float, float]
|
|
||||||
|
|
||||||
|
|
||||||
def lerped_amount(x: float, edges: Range) -> float:
|
|
||||||
"""How far `x` is between `values`"""
|
|
||||||
return (x - edges[0]) / (edges[1] - edges[0])
|
|
||||||
|
|
||||||
|
|
||||||
def smoothstep(x: float, edges: Range) -> float:
|
|
||||||
"""Smoothly interpolates between the `edges` based on `x`"""
|
|
||||||
# Technically I should bounds check but whatever
|
|
||||||
return (x * x * (3.0 - 2.0 * x)) * (edges[1] - edges[0]) + edges[0]
|
|
||||||
|
|
||||||
|
|
||||||
def day_elapsed(hours: int = 0, minutes: int = 0, seconds: int = 0) -> float:
|
|
||||||
"""Converts (H, M, S) into [0, 1] representing how far through the day it is"""
|
|
||||||
return ((((hours * 60) + minutes) * 60) + seconds) / 86400
|
|
||||||
|
|
||||||
|
|
||||||
def now_elapsed() -> float:
|
|
||||||
"""How far through the day it is"""
|
|
||||||
now = datetime.now()
|
|
||||||
return day_elapsed(now.hour, now.minute, now.second)
|
|
||||||
|
|
||||||
def calculate_temperature(sunrise: Range, sunset: Range, temperature_range: Range) -> float:
|
|
||||||
"""What temperature the monitor should be"""
|
|
||||||
elapsed = now_elapsed()
|
|
||||||
|
|
||||||
if elapsed <= sunrise[0]:
|
|
||||||
return temperature_range[0]
|
|
||||||
elif elapsed < sunrise[1]:
|
|
||||||
return smoothstep(lerped_amount(elapsed, sunrise), temperature_range)
|
|
||||||
elif elapsed <= sunset[0]:
|
|
||||||
return temperature_range[1]
|
|
||||||
elif elapsed < sunset[1]:
|
|
||||||
return smoothstep(lerped_amount(elapsed, sunset), (temperature_range[1], temperature_range[0]))
|
|
||||||
else:
|
|
||||||
return temperature_range[0]
|
|
||||||
|
|
||||||
|
|
||||||
def setup_socket() -> socket:
|
|
||||||
"""Connects to the Hyprsunset socket"""
|
|
||||||
hyprsunset = socket(AF_UNIX, SOCK_STREAM)
|
|
||||||
# In theory I should use $XDG_RUNTIME_DIR, but for me it's always `/run/user/1000/`
|
|
||||||
hyprsunset.connect(f"{environ["XDG_RUNTIME_DIR"]}/hypr/{environ["HYPRLAND_INSTANCE_SIGNATURE"]}/.hyprsunset.sock")
|
|
||||||
return hyprsunset
|
|
||||||
|
|
||||||
|
|
||||||
def get_temperature(hyprsunset: socket) -> int:
|
|
||||||
"""Retrieves the current screen temperature"""
|
|
||||||
# In theory the message might not send in one go, but in practice it does so I won't bother handling the error
|
|
||||||
_ = hyprsunset.send(b"temperature")
|
|
||||||
# 4 bytes should be enough but why not 8 for comfort
|
|
||||||
# Just raise an error if it's not a number, nothing special to do here
|
|
||||||
return int(hyprsunset.recv(8))
|
|
||||||
|
|
||||||
|
|
||||||
def set_temperature(hyprsunset: socket, temperature: float, /, instant: bool = False) -> None:
|
|
||||||
# In theory the message might not send in one go, but in practice it does so I won't bother handling the error
|
|
||||||
_ = hyprsunset.send(f"temperature {temperature:.1f}".encode())
|
|
||||||
if instant:
|
|
||||||
# Setting the temperature twice in quick succession sometimes skips the transition period
|
|
||||||
set_temperature(hyprsunset, temperature, instant=False)
|
|
||||||
|
|
||||||
|
|
||||||
def on_steam() -> bool:
|
|
||||||
"""Whether the Steam PID is in use"""
|
|
||||||
pid_file = Path("~/.steampid").expanduser().resolve(strict=True)
|
|
||||||
pid = pid_file.read_text().strip()
|
|
||||||
pid_dir = Path("/proc") / pid
|
|
||||||
return pid_dir.is_dir()
|
|
||||||
|
|
||||||
|
|
||||||
def main(sunrise: Range, sunset: Range, temperature_range: Range, /, check_steam: bool = False, instant: bool = False) -> None:
|
|
||||||
"""Adjusts the monitor temperature based on the current time"""
|
|
||||||
hyprsunset = setup_socket()
|
|
||||||
temperature = int(calculate_temperature(sunrise, sunset, temperature_range))
|
|
||||||
if temperature != get_temperature(hyprsunset):
|
|
||||||
if check_steam and on_steam():
|
|
||||||
return
|
|
||||||
set_temperature(hyprsunset, temperature, instant=instant)
|
|
||||||
|
|
||||||
|
|
||||||
class Arguments(Namespace):
|
class Arguments(Namespace):
|
||||||
check_steam: bool = False
|
check_steam: bool = False
|
||||||
instant: bool = False
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
|
def call_from_args() -> None:
|
||||||
parser = ArgumentParser(description="Adjusts the temperature of your screen based on the time of day")
|
parser = ArgumentParser(description="Adjusts the temperature of your screen based on the time of day")
|
||||||
_ = parser.add_argument("-s", "--check-steam", action="store_true", help="Don't adjust temperature if Steam is active")
|
_ = parser.add_argument("-s", "--check-steam", action="store_true", help="Don't adjust temperature if Steam is active")
|
||||||
_ = parser.add_argument("-i", "--instant", action="store_true", help="Try to instantly change temperature")
|
|
||||||
args = cast(Arguments, parser.parse_args())
|
args = cast(Arguments, parser.parse_args())
|
||||||
main(
|
main(
|
||||||
(day_elapsed(5), day_elapsed(7)),
|
|
||||||
(day_elapsed(21), day_elapsed(23)),
|
|
||||||
(2500.0, 6000.0),
|
(2500.0, 6000.0),
|
||||||
check_steam=args.check_steam,
|
check_steam=args.check_steam,
|
||||||
instant=args.instant
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
call_from_args()
|
||||||
|
|
|
@ -3,4 +3,4 @@ Description=Sets the monitor temperature based on the time of day
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
ExecStart=/bin/env python -O /home/mbradley/scripts/sunset.py --check-steam
|
ExecStart=/bin/env python -OO /home/mbradley/scripts/sunset.py --check-steam
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue