#!/usr/bin/python
import os
import time
import math
import smbus
import tkinter as tk
import pigpio
import datetime
# ================= GPIO =================
FEEDBACK_PIN_0 = 20
FEEDBACK_PIN_1 = 21
MIN_US = 32
MAX_US = 1068
# ================= pigpio =================
pi = pigpio.pi()
if not pi.connected:
exit(„Could not connect to pigpiod”)
pi.set_mode(FEEDBACK_PIN_0, pigpio.INPUT)
pi.set_mode(FEEDBACK_PIN_1, pigpio.INPUT)
# ================= STANY =================
servo_running = {0: False, 1: False}
last_tick = {0: None, 1: None}
current_angle = {0: 0.0, 1: 0.0}
last_displayed_angle = {0: 0.0, 1: 0.0}
day_to_display = 0
hour_to_display = -1
# ================= CALLBACK =================
def make_callback(servo_id):
def cbf(gpio, level, tick):
if level == 1:
last_tick[servo_id] = tick
elif level == 0 and last_tick[servo_id] is not None:
dt = pigpio.tickDiff(last_tick[servo_id], tick)
angle = (dt – MIN_US) * 360.0 / (MAX_US – MIN_US)
angle = max(0, min(360, angle))
current_angle[servo_id] = 0.8 * current_angle[servo_id] + 0.2 * angle
return cbf
pi.callback(FEEDBACK_PIN_0, pigpio.EITHER_EDGE, make_callback(0))
pi.callback(FEEDBACK_PIN_1, pigpio.EITHER_EDGE, make_callback(1))
# ================= PCA9685 =================
class PCA9685:
__MODE1 = 0x00
__PRESCALE = 0xFE
__LED0_ON_L = 0x06
def __init__(self, address=0x40):
self.bus = smbus.SMBus(1)
self.address = address
self.write(self.__MODE1, 0x00)
def write(self, reg, value):
self.bus.write_byte_data(self.address, reg, value)
def read(self, reg):
return self.bus.read_byte_data(self.address, reg)
def setPWMFreq(self, freq):
prescaleval = 25000000.0 / 4096.0 / freq – 1.0
prescale = math.floor(prescaleval + 0.5)
oldmode = self.read(self.__MODE1)
self.write(self.__MODE1, (oldmode & 0x7F) | 0x10)
self.write(self.__PRESCALE, int(prescale))
self.write(self.__MODE1, oldmode)
time.sleep(0.005)
self.write(self.__MODE1, oldmode | 0x80)
def setPWM(self, channel, on, off):
self.write(self.__LED0_ON_L + 4 * channel, on & 0xFF)
self.write(self.__LED0_ON_L + 4 * channel + 1, on >> 8)
self.write(self.__LED0_ON_L + 4 * channel + 2, off & 0xFF)
self.write(self.__LED0_ON_L + 4 * channel + 3, off >> 8)
def setServoPulse(self, channel, pulse):
pulse = pulse * 4096 / 20000
self.setPWM(channel, 0, int(pulse))
pwm = PCA9685(0x40)
pwm.setPWMFreq(50)
# ================= RUCH DO CELU =================
target_angle = {0: None, 1: None}
def move_to_target(servo_id):
if target_angle[servo_id] is None:
return
current = current_angle[servo_id]
target = target_angle[servo_id]
error = target – current
if error < 0:
error += 360
if error < 5:
pwm.setServoPulse(servo_id, 0)
target_angle[servo_id] = None
return
pwm.setServoPulse(servo_id, 1575)
root.after(20, lambda: move_to_target(servo_id))
# ================= KONWERSJE (NAPRAWIONE) =================
def hour_to_angle(h):
return (h / 24.0) * 360.0
def day_to_angle(day):
return ((day - 1) / 365.0) * 360.0
def angle_to_day(angle):
return int((angle / 360.0) * 365.0) + 1
def angle_to_hour(angle):
return (angle / 360.0) * 24.0
# ================= AKCJE =================
def go_to_hour(h):
target_angle[1] = hour_to_angle(24-h)
global hour_to_display
hour_to_display = h
move_to_target(1)
def go_to_day(d):
target_angle[0] = day_to_angle(365-d)
global day_to_display
day_to_display = d
move_to_target(0)
def go_now():
now = datetime.datetime.now()
target_angle[1] = hour_to_angle(24-now.hour)
global hour_to_display
hour_to_display = int(now.hour)
move_to_target(1)
target_angle[0] = day_to_angle(365-now.timetuple().tm_yday)
global day_to_display
day_to_display = now.timetuple().tm_yday
move_to_target(0)
# ================= LOGIKA =================
def toggle_servo(ch):
if not servo_running[ch]:
servo_running[ch] = True
pwm.setServoPulse(ch, 1575)
if ch == 0:
buttondates.config(text="Obrót roczny STOP")
else:
buttonhours.config(text="Obrót dobowy STOP")
else:
servo_running[ch] = False
pwm.setServoPulse(ch, 0)
if ch == 0:
buttondates.config(text="Obrót roczny START")
else:
buttonhours.config(text="Obrót dobowy START")
# ================= UPDATE GUI =================
def update_labels():
if abs(current_angle[1] - last_displayed_angle[1]) >= 5:
last_displayed_angle[1] = current_angle[1]
hour = angle_to_hour((360 – current_angle[1]) % 360)
h = int(hour)
m = int((hour – h) * 60)
hours_label.config(text=f”{h:02d}:{m:02d}”)
if hour_to_display > -1:
if abs(hour – hour_to_display) < 1 or abs(hour - hour_to_display) > 23:
hour = hour_to_display
hours_label.config(text=f”{hour:02d}:00″)
if abs(current_angle[0] – last_displayed_angle[0]) >= 5:
last_displayed_angle[0] = current_angle[0]
day = angle_to_day((360 – current_angle[0]) % 360)
if day_to_display > 0:
if abs(day – day_to_display) < 20 or abs(day - day_to_display) > 345:
day = day_to_display
date = datetime.date(2026, 1, 1) + datetime.timedelta(days=day – 1)
months = [
„stycznia”,”lutego”,”marca”,”kwietnia”,”maja”,”czerwca”,
„lipca”,”sierpnia”,”września”,”października”,”listopada”,”grudnia”
]
dates_label.config(text=f”{date.day} {months[date.month – 1]}”)
root.after(20, update_labels)
# ================= GUI =================
root = tk.Tk()
root.configure(bg=”black”)
root.attributes(„-fullscreen”, True)
def cleanup_and_exit(event=None):
pwm.setServoPulse(0, 0)
pwm.setServoPulse(1, 0)
pi.stop()
root.destroy()
root.bind(„
label = tk.Label(root, text=”Jak wygląda oświetlenie Ziemi?”,
font=(„DejaVu Sans”, 28, „bold”),
bg=”black”, fg=”white”)
label.grid(row=1, column=1, columnspan=2, pady=16)
buttondates = tk.Button(root, text=”Obrót roczny START”,
font=(„DejaVu Sans”, 20),
command=lambda: toggle_servo(0))
buttondates.grid(row=2, column=1)
buttonhours = tk.Button(root, text=”Obrót dobowy START”,
font=(„DejaVu Sans”, 20),
command=lambda: toggle_servo(1))
buttonhours.grid(row=2, column=2)
tk.Button(root, text=”21 marca”, font=(„DejaVu Sans”, 20),
command=lambda: go_to_day(80)).grid(row=3, column=1)
tk.Button(root, text=”22 czerwca”, font=(„DejaVu Sans”, 20),
command=lambda: go_to_day(173)).grid(row=4, column=1)
tk.Button(root, text=”23 września”, font=(„DejaVu Sans”, 20),
command=lambda: go_to_day(266)).grid(row=5, column=1)
tk.Button(root, text=”22 grudnia”, font=(„DejaVu Sans”, 20),
command=lambda: go_to_day(356)).grid(row=6, column=1)
tk.Button(root, text=”00:00″, font=(„DejaVu Sans”, 20),
command=lambda: go_to_hour(0)).grid(row=3, column=2)
tk.Button(root, text=”06:00″, font=(„DejaVu Sans”, 20),
command=lambda: go_to_hour(6)).grid(row=4, column=2)
tk.Button(root, text=”12:00″, font=(„DejaVu Sans”, 20),
command=lambda: go_to_hour(12)).grid(row=5, column=2)
tk.Button(root, text=”18:00″, font=(„DejaVu Sans”, 20),
command=lambda: go_to_hour(18)).grid(row=6, column=2)
tk.Button(root, text=”TERAZ”, font=(„DejaVu Sans”, 20),
bg=”orange”, command=go_now).grid(row=7, column=1, columnspan=2, pady=10)
dates_label = tk.Label(root, text=”1 stycznia”,
font=(„DejaVu Sans”, 20),
fg=”cyan”, bg=”black”)
dates_label.grid(row=8, column=1)
hours_label = tk.Label(root, text=”00:00″,
font=(„DejaVu Sans”, 20),
fg=”cyan”, bg=”black”)
hours_label.grid(row=8, column=2)
buttonclose = tk.Button(root, text=”Zakończ”,
font=(„DejaVu Sans”, 20),
command=lambda: os.system(’sudo poweroff’))
buttonclose.grid(row=9, column=1, columnspan=2)
update_labels()
root.mainloop()
def stop_servo(ch):
servo_running[ch] = False
pwm.setServoPulse(ch, 0)
if ch == 0:
buttondates.config(text=”Obrót roczny START”)
else:
buttonhours.config(text=”Obrót dobowy START”)
def toggle_servo(ch):
if not servo_running[ch]:
servo_running[ch] = True
pwm.setServoPulse(ch, 1575)
if ch == 0:
buttondates.config(text=”Obrót roczny STOP”)
else:
buttonhours.config(text=”Obrót dobowy STOP”)
# ⏱ zatrzymaj po 30 sekundach (30000 ms)
root.after(30000, lambda: stop_servo(ch))
else:
stop_servo(ch)
